如何在 laravel 中为列表或数组定义策略?

Posted

技术标签:

【中文标题】如何在 laravel 中为列表或数组定义策略?【英文标题】:How to define policy for a list or array in laravel? 【发布时间】:2019-10-03 14:46:42 【问题描述】:

我有以下政策来确定用户是否能够查看合同。

public function view(User $user, Contract $contract)
    
        if ($user->user_type->id == 2)  // If user is a vecino
            if ($user->id == $contract->customer_id) 
                return true;
            
         else if ($user->user_type->is_admin == true)  // If user is an admin
            return true;
        

        return false;
    

然后检查授权

$this->authorize('view', $contract);

如何检查列表/数组/集合的授权?就像我通过 Contract::all() 获得一份合同清单一样

我还没有找到任何方法来做到这一点。我可以做一个循环并为每次迭代调用 $this->authorize 来检查授权,但这可能会影响性能。

有更好的方法吗?

【问题讨论】:

您如何确定用户是否可以查看合同?有关系吗? 如果是普通用户,合约里面必须要有用户的id。至于管理员,它应该总是被允许的。它已经显示在我上面的代码中,它检查用户 ID 是否等于合同的客户 ID。 你有没有想过这个问题和/或我的回答有帮助吗? 【参考方案1】:

我在这种情况下经常看到的设计,是通过策略检查是否允许查看查询中的所有元素。这不能很好地扩展并且不适合分页。

与其过滤掉带有策略的合约,更好的解决方案是过滤查询中已经存在的合约。这主要是因为如果您想进行分页,您希望在执行查询之前进行所有过滤,以避免出现奇怪的分页元数据。同时还必须为每个元素运行 n 次操作,这在 1000 个元素中已经是个问题了。

这里做下面的查询子句,可以得到和你的策略一样的结果。

Contract::where('user_id', $user->id)->get();

为了让我自己更轻松,我通常会做的一个版本是在用户模型中创建一个范围。

public function scopeOwned($query, User $user)

    return $this->query->where('user_id', $user->id);


Contract::owned($user)->get();

【讨论】:

所以解决方案是将您的策略​​逻辑复制到范围中?我不是说你错了,但应该有比这更好的方法。 但是您不能为 all() / paginate 结果定义策略。然后,您必须针对您返回的 n 个元素检查策略。此外,如果您进行分页,您将很难在不破坏标准分页方法的情况下应用您的策略。更不用说搜索,与分页和策略相结合并不一定很有趣。对于 show() rest api 方法,当然要制定策略。你有更好的建议吗,我很好奇这里:) 同意政策不应用于收藏;这就是范围的用途。应该使用策略来验证对特定模型或操作的授权。我真正关心的是 where 放置授权逻辑,以便它可以在不同的地方重用。必须在策略和范围中都写它会很糟糕。我发现的最干净/最简单的解决方案是将您的授权逻辑放入一个范围(如您所建议的那样),然后从策略中引用该范围。我发布了我的解决方案。干杯。【参考方案2】:

你必须循环,一种方式到另一种方式。在您的控制器或您的策略中循环遍历 Contract 对象没有区别,但是制定了策略来检查单个资源,所以我会在您的控制器中执行此操作。

【讨论】:

【参考方案3】:

我目前使用的一个解决方案是一种混合方法,您可以在一个范围内定义您的规则,然后从策略中引用该范围,从而允许您重用您的授权逻辑。

// Contract model

public function scopeViewable($query)

    // If the user is admin, just return the query unfiltered.
    if (Auth::user()->user_type->is_admin) 
        return $query;
    

    // Check the contract belongs to the logged in user. 
    return $query->where('customer_id', Auth::id());

然后在您的策略中,引用该范围,但将其限制为当前模型。确保使用 exists() 返回一个布尔值。这实质上是检查您的模型是否可见

// Contract Policy

public function view(User $user, Contract $contract)

    return Contract::viewable()
        ->where('id', $contract->id)
        ->exists()
    ;

重要的是,您应该在检索模型集合时使用 范围,而不是为集合中的每个模型运行范围查询的策略。策略应该用于单个模型实例。

Contract::viewable()->paginate(10);

// Or
Contract::viewable()->get();

但是,当您想要检查个人合同时,您可以直接使用您的保单。

$this->authorize('view', $contract);

// Or
Auth::user()->can('view', [Contract::class, $contract]);

【讨论】:

以上是关于如何在 laravel 中为列表或数组定义策略?的主要内容,如果未能解决你的问题,请参考以下文章

在 Laravel 中为博客生成归档列表

我如何在 laravel 中为 spatie 包(ACL 管理)中的其他角色分配用户列表权限?

如何在我的 GraphQL 中为对象中的对象列表定义类型

如何在 CodeIgniter 中为 Laravel 中的第一个项目定义子域路由?

如何在 Laravel 5.5 中为选定的请求类设置自定义响应

如何在我的GraphQL中为对象中的对象列表定义类型