timacdonald/has-parameters

一个特质,允许你以更PHP风格的方式向Laravel中间件传递参数。

v1.5.1 2024-05-18 07:27 UTC

This package is auto-updated.

Last update: 2024-08-26 22:46:56 UTC


README

Has Parameters: a Laravel package by Tim MacDonald

参数化

Laravel中间件的特质,允许你以更PHP风格的方式传递参数,包括作为键 => 值对的命名参数,以及作为列表的变长参数。改进了静态分析/IDE支持,允许你通过引用参数名称来指定参数,可以跳过可选参数(将回退到其默认值),并添加了一些验证,以防止你意外忘记任何必需的参数。

更多关于原因的信息,请参阅我的博客文章 重新思考Laravel的中间件参数API

安装

你可以使用 composer 来安装

composer require timacdonald/has-parameters

基本用法

为了开始使用示例,我将使用Laravel的ThrottleRequests的一个简化版本。首先,将HasParameters特质添加到你的中间件中。

class ThrottleRequests
{
    use HasParameters;

    public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1, $prefix = '')
    {
        //
    }
}

现在,你可以使用静态的with()方法,使用参数名称作为键来向这个中间件传递参数。

Route::stuff()
    ->middleware([
        ThrottleRequests::with([
            'maxAttempts' => 120,
        ]),
    ]);

一开始你可能觉得这有点冗长,但我觉得在阅读这些文档并亲自尝试之后,你会喜欢这个完整的特性集。

Middleware::with()

静态的with()方法允许你在声明中间件时轻松地看到哪些值代表什么,而不是仅仅声明一个逗号分隔的值列表。键的顺序并不重要。特质会将键与handle()方法中的参数名称配对。

// before...
Route::stuff()
    ->middleware([
        'throttle:10,1' // what does 10 or 1 stand for here?
    ]);

// after...
Route::stuff()
    ->middleware([
        ThrottleRequests::with([
            'decayMinutes' => 1,
            'maxAttempts' => 10,
        ]),
    ]);

跳过参数

如果handle方法中的任何参数有默认值,你不需要传递它们,除非你想改变它们的值。例如,如果你想只为ThrottleRequests中间件指定一个前缀,但保留$decayMinutes$maxAttempts的默认值,你可以这样做...

Route::stuff()
    ->middleware([
        ThrottleRequests::with([
            'prefix' => 'admins',
        ]),
    ]);

正如我们在之前的handle方法中看到的,$decayMinutes的默认值是1,而$maxAttempts的默认值是60。中间件将接收到这些参数的值,但现在将接收到"admins"作为$prefix

数组用于变长参数

当你的中间件以变长参数结尾时,你可以传递一个包含值数组的变长参数键。看看下面的handle()方法。

public function handle(Request $request, Closure $next, string $ability, string ...$models)

这是我们将如何向变长的$models参数传递值列表的方法...

Route::stuff()
    ->middleware([
        Authorize::with([
            'ability' => PostVideoPolicy::UPDATE,
            'models' => [Post::class, Video::class],
        ]),
    ]);

参数别名

某些中间件将根据传递给特定参数的值的类型有不同的行为。例如,Laravel的ThrottleRequests中间件允许你将速率限制器的名称传递给$maxAttempts参数,而不是一个数值,以便在端点上利用该命名限制器。

// a named rate limiter...

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
});

// using the rate limiter WITHOUT an alias...

Route::stuff()
    ->middleware([
        ThrottleRequests::with([
            'maxAttempts' => 'api',
        ]),
    ]);

在这种场景下,能够将$maxAttempts参数名称别名为更易读的名称会很好。

Route::stuff()
    ->middleware([
        ThrottleRequests::with([
            'limiter' => 'api',
        ]),
    ]);

为了实现这一点,你可以在你的中间件中设置一个参数别名映射...

class ThrottleRequests
{
    use HasParameters;

    public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1, $prefix = '')
    {
        //
    }

    protected static function parameterAliasMap(): array
    {
        return [
            'limiter' => 'maxAttempts',
            // 'alias' => 'parameter',
        ];
    }
}

验证

这些验证发生在加载或编译路由文件时,而不仅仅是当你访问包含声明的路由时。

意外的参数

确保你不在 handle() 方法中声明任何不存在的参数变量键。这有助于确保你不会误输参数名。

必需参数

确保所有必需参数(没有默认值的参数)都已提供。

别名

  • 确保所有指定的别名都引用了一个现有的参数。
  • 提供的别名不引用相同的参数。
  • 原始参数键和别名没有同时提供。

Middleware::in()

静态的 in() 方法在很大程度上反映了现有的连接 API,并且以相同的方式工作。它接受一个值列表,即非关联数组。如果你的 handle() 方法是一个单个可变参数,即期望一个值列表,你应该使用此方法,如下面的中间件处理方法所示...

public function handle(Request $request, Closure $next, string ...$states)
{
    //
}

你可以像这样将一个“状态”列表传递给中间件...

Route::stuff()
    ->middleware([
        EnsurePostState::in([PostState::DRAFT, PostState::UNDER_REVIEW]),
    ]);

验证

必需参数

就像 with() 方法一样,in() 方法将验证你是否已传递足够多的值来覆盖所有必需的参数。因为可变参数不需要传递任何值,所以你只有在应该使用 with() 方法时才会真正遇到这个问题。

值转换

你应该记住,所有内容仍然会被转换成字符串。虽然你传递的是整数,但中间件本身始终会接收一个字符串。这是 Laravel 为了实现路由缓存而在底层将所有内容转换为字符串的方式。

需要注意的是,false 实际上会被转换为字符串 "0",以保持与将 true 转换为字符串 "1" 的一致性。

类型值

你可以通过在你的中间件类上使用文档块来提供更强的类型信息。以下是如何创建一个强类型中间件的示例

/**
 * @method static string with(array{
 *     maxAttempts?: int,
 *     decayMinutes?: float|int,
 *     prefix?: string,
 * }|'admin' $arguments)
 */
class ThrottleMiddleware
{
    use HasParameters;

    // ...
}

然后你将接收到来自你的语言服务器的自动完成和诊断

ThrottleMiddleware::with('admin');
// ✅

ThrottleMiddleware::with(['decayMinutes' => 10]);
// ✅

ThrottleMiddleware::with('foo');
// ❌ fails because 'foo' is not in the allowed string values

ThrottleMiddleware::with(['maxAttempts' => 'ten']);
// ❌ fails because `maxAttempts` must be an int

请查看 PHPStan playground 中的示例。

鸣谢

特别感谢 Caneco 为其标志提供支持(vegi)✨

致谢

你可以自由使用这个包,但我要求你联系某人(不是我自己),他们以前或目前正在维护或为你在项目中使用的开源库做出贡献,并感谢他们的工作。请考虑你的整个技术栈:包、框架、语言、数据库、操作系统、前端、后端等。