Laravel 没有全局范围的路由模型绑定

Laravel route model binding without global scope

我的 laravel 8.0 应用程序中有以下路线组:

Route::prefix('offline_transaction')->name('offline_transaction.')->group(function () {
    Route::post('/approve/{transaction:uuid}', [OfflineTransactionController::class, 'approve'])
        ->name('approve');

    Route::post('/reject/{transaction:uuid}', [OfflineTransactionController::class, 'reject'])
        ->name('reject');
});

Transaction模型是:


class Transaction extends Model implements CreditBlocker
{
    //....
    protected static function boot()
    {
        parent::boot();
        static::addGlobalScope(new AuthUserScope());
    }
    //....
}

这是我的 AuthUserScope:

class AuthUserScope implements Scope
{
    private string $fieldName;

    public function __construct($fieldName = 'user_id')
    {
        $this->fieldName = $fieldName;
    }

    public function apply(Builder $builder, Model $model)
    {
        $user = Auth::user();
        if ($user) {
            $builder->where($this->fieldName, $user->id);
        }
    }
}

现在的问题是,当管理员想要批准或拒绝 transaction 时,将抛出 404 Not found 错误。我怎样才能通过这个?

Customizing The Resolution Logic

If you wish to define your own model binding resolution logic, you may use the Route::bind method. The closure you pass to the bind method will receive the value of the URI segment and should return the instance of the class that should be injected into the route. Again, this customization should take place in the boot method of your application's RouteServiceProvider:

解决方案

您可以做的是更改 routes/web.php 文件中特定路线的参数名称。

Route::prefix('offline_transaction')->name('offline_transaction.')->group(function () {
    Route::post('/approve/{any_transaction}', [OfflineTransactionController::class, 'approve'])
        ->name('approve');

    Route::post('/reject/{any_transaction}', [OfflineTransactionController::class, 'reject'])
        ->name('reject');

注意 any_transaction。将其更改为您认为最方便的任何命名约定。

然后,在您的 app/Providers/RouteServiceProvider.php 文件中,将您的 boot(...) 方法更改为如下内容:

use App\Models\Transaction;
use Illuminate\Support\Facades\Route;
// ...
    public function boot()
    {
       // ...

        Route::bind('any_transaction', function($uuid) {
            return Transaction::withoutGlobalScopes()->where('uuid', $uuid)->firstOrFail();
        });

       // ...
    }
// ...

然后在您的控制器 app/Http/Controllers/OfflineTransactionController.php 文件中,访问注入的模型:

use App\Models\Transaction;
// ...

public function approve(Transaction $any_transaction) {

// ...

}

// ...

致谢:Using Route Model Binding without Global Scope @thomaskim

附录

如果你想remove a specific global scope从路由模型绑定查询,你可以使用 withoutGlobalScope(AuthUserScope::class)app/Providers/RouteServiceProvider.php 文件的 boot(...) 方法中。

另一种方法是我可以在 AuthUserScope class 中使用 Route::currentRouteNamed 如下,我更喜欢使用它而不是 Route::bind:

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

class AuthUserScope implements Scope
{
    private string $fieldName;

    public function __construct($fieldName = 'user_id')
    {
        $this->fieldName = $fieldName;
    }

    public function apply(Builder $builder, Model $model)
    {
        $user = Auth::user();
        if ($user && !Route::currentRouteNamed('admin.*')) {
            $builder->where($this->fieldName, $user->id);
        }
    }
}