Laravel - 如何将附加参数传递给 Gate 以保持其原始类型?

Laravel - How to pass additional parameters to Gate keeping their original types?

我正在尝试从某个控制器向 Gate 传递一个附加参数。

我得到的一个错误和部分stack trace如下:

testing.ERROR: App\Providers\AuthServiceProvider::App\Providers\{closure}(): Argument #2 ($request) must be of type Illuminate\Http\Request, string given, called in /var/www/html/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php on line 477 {"exception":"[object] (TypeError(code: 0): App\Providers\AuthServiceProvider::App\Providers\{closure}(): Argument #2 ($request) must be of type Illuminate\Http\Request, string given, called in /var/www/html/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php on line 477 at /var/www/html/app/Providers/AuthServiceProvider.php:74)
[stacktrace]
#0 /var/www/html/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php(477): App\Providers\AuthServiceProvider->App\Providers\{closure}(NULL, 'Illuminate\\Http...')
#1 /var/www/html/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php(372): Illuminate\Auth\Access\Gate->callAuthCallback(NULL, 'foo', Array)
#2 /var/www/html/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php(337): Illuminate\Auth\Access\Gate->raw('foo', Array)
#3 /var/www/html/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php(324): Illuminate\Auth\Access\Gate->inspect('foo', Array)
#4 /var/www/html/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authorize.php(43): Illuminate\Auth\Access\Gate->authorize('foo', Array)
#5 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\Auth\Middleware\Authorize->handle(Object(Illuminate\Http\Request), Object(Closure), 'foo', 'Illuminate\\Http...')
#6 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#7 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\Routing\Middleware\SubstituteBindings->handle(Object(Illuminate\Http\Request), Object(Closure))
#8 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(127): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#9 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(103): Illuminate\Routing\Middleware\ThrottleRequests->handleRequest(Object(Illuminate\Http\Request), Object(Closure), Array)
#10 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(55): Illuminate\Routing\Middleware\ThrottleRequests->handleRequestUsingNamedLimiter(Object(Illuminate\Http\Request), Object(Closure), 'api', Object(Closure))

问题是 $request(如下所示)以字符串形式出现 Gate,而不是 Request 类型。在 Gate 中使用 dd 函数(并暂时删除类型提示),我发现 $request 的值只是一个无用的文本:"Illuminate\Http\Request".

如何将 $request 传递给 Gate 并保持 Request 类型?

SomeController.php

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use App\Http\Controllers\Controller;

class SomeController extends Controller
{
    public function fooEvents(Request $request): Response
    {
        /* some processes */
    }
}

AuthServiceProvider.php

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

public function boot()
{
    $this->registerPolicies();

    /**
     * @param \App\Models\User $user
     * @param Illuminate\Http\Request $request
     * @return bool
     */
    Gate::define('foo', function (?User $user, Request $request) {

        /**
         * The problem is that the argument $request comes here as string type,
         * while I want it as Illuminate\Http\Request type;
         */

        /* some processes */
    });
}

api.php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\SomeController;

Route::post('/foo-events', [SomeController::class, 'fooEvents'])
    ->middleware('can:foo,Illuminate\Http\Request')
    ->name('foo-events');

注:

如果我直接在控制器内部使用Gate(如下所示),它工作正常。但是,由于我们团队的规定,我必须通过 api.php 使用 Gate。你有什么想法吗?

SomeController.php(一个工作示例)

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Gate;

class SomeController extends Controller
{
    public function fooEvents(Request $request): Response
    {
        if (!Gate::allows('foo', $request)) {
            return abort(404);
        }

        /* some processes */
    }
}

AuthServiceProvider.php(一个工作示例)

(同上)

api.php(一个工作示例)

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\SomeController;

Route::post('/foo-events', [SomeController::class, 'fooEvents'])
    ->name('foo-events');

我对解析为 Illuminate\Auth\Middleware\Authorizecan 中间件进行了一些测试。

如果提供了类名(路由模型绑定)或路由参数(例如:my_route/{my_param},您可以用can:foo,my_param).

如您所述,如果您的门是从您的控制器调用的,您可以将请求作为参数传递。但是从使用“can”中间件的路由定义中,我无法将整个请求对象作为参数。

您仍然可以在门方法中使用助手 request() 来访问整个请求。

一些细节here和阅读src/Illuminate/Auth/Middleware/Authorize.phpgetGateArguments()方法。

请注意,您还可以将政策附加到您的大门,并使用 request() 助手。

我对门不是很熟悉,但是 can 中间件与策略一起使用。它可以传递参数,但这些是路由参数,如 the canonical example:

Route::put('/post/{post}', function (Post $post) {
    // The current user may update the post...
})->middleware('can:update,post');

post 被定义为路由参数,因此当它被添加到 can 中间件时,该模型作为参数传递给策略。

在不需要模型实例的情况下,class 的名称将作为参数传递给策略,除非使用路由模型绑定,在这种情况下它将是一个新实例给定的 class。同样,这被政策使用,而不是 AFAIK 门,并且不会给你当前的请求对象。


获取当前请求实例的常用方法是使用the request() helper。只需将您的代码修改为如下所示:

Gate::define('foo', function (?User $user) {
    $request = request();
    /* some processes */
});