cura / user-policy-bundle
1.2.0
2024-01-11 08:43 UTC
Requires
- php: >=8.2
- geekcell/container-facade: ^1.0
- symfony/config: ^6.4|^7.0
- symfony/dependency-injection: ^6.4|^7.0
- symfony/http-kernel: ^6.4|^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.46
- phpstan/phpstan: ^1.10
- phpstan/phpstan-phpunit: ^1.3
- phpunit/phpunit: ^10.5
README
这个包受到GeekCell 用户策略包的启发,并针对 Cura4You 面临的使用场景进行了适配。
示例
假设你正在开发一个视频平台,用户只有拥有高级订阅或剩余上传配额时才允许上传。
use Cura\UserPolicyBundle\Policy\AbilityNotSupportedRejection; use Cura\UserPolicyBundle\Policy\Rejected; use Cura\UserPolicyBundle\Policy\Granted; use Cura\UserPolicyBundle\Policy\RejectionReason; #[AsPolicy(Video::class)] /** @implements Policy<User, Video> */ class VideoPolicy implements Policy { public function __construct( private readonly QuotaService $quotaService, ) { } public function canDo( $instance, string $ability, $subject, mixed ...$arguments ): Granted|Rejected { return match ($ability) { 'upload' => $this->upload($instance), default => new Rejected(new AbilityNotSupportedRejection()) }; } public function upload(User $user): Granted { if ($user->hasPremiumSubscription()) { return new Granted(); } if ($this->quotaService->getRemainingUserUploads($user) > 0) { return new Granted(); } return new Rejected(new RejectionReason('User has no premium subscription or remaining quote')); } }
use Cura\UserPolicyBundle\Policy\Granted; class VideoController extends AbstractController { #[Route('/videos/new_upload')] public function create(): Response { if ($this->getUser()->can('upload', Video::class) instanceof Granted) { // Proceed with upload ... } $this->createAccessDeniedException('Operation not allowed.'); } }
非常好,不是吗?业务逻辑封装在策略类中,可以直接从用户对象中神奇地查询。
安装
要使用此包,请在 Composer 中要求它
composer require cura/user-policy-bundle
安装后,在您的 config/services.yaml
中添加以下行
services: # Add these lines below to your services.yaml _instanceof: Cura\UserPolicyBundle\Contracts\Policy: tags: ['cura.user_policy.policy']
这些行对于 Symfony 自动发现您应用程序中定义的策略至关重要。或者,策略可以手动配置,甚至可以根据名称猜测,但这些方法不推荐使用。
现在将 PolicyResolverTrait
特性添加到您的用户类中。
<?php namespace App\Security; use Cura\UserPolicyBundle\Trait\PolicyResolverTrait; class User { use PolicyResolverTrait; }
现在您可以开始了。
编写策略
一个基本策略看起来像这样
<?php namespace App\Security\Policy; use App\Entity\Book; use App\Security\User; use Cura\UserPolicyBundle\Policy\AbilityNotSupportedRejection; use Cura\UserPolicyBundle\Policy\Granted;use Cura\UserPolicyBundle\Policy\Rejected; use GeekCell\UserPolicyBundle\Contracts\Policy; use GeekCell\UserPolicyBundle\Support\Attribute\AsPolicy; #[AsPolicy(Book::class)] /** @implements Policy<User, Book> */ class BookPolicy implements Policy { public function canDo( $instance, string $ability, $subject, mixed ...$arguments ): Granted|Rejected { return match ($ability) { 'create' => $this->create($instance), 'update' => $this->update($instance, $subject), 'delete' => $this->delete($instance, $subject, $arguments), default => new Rejected(new AbilityNotSupportedRejection()); } } public function create(User $user): Granted|Rejected { // ... } public function update(User $user, Book $book): Granted|Rejected { // ... } public function delete(User $user, Book $book, mixed $someArguments): Granted|Rejected { // ... } }
让我们来分解它
- 策略必须实现
Cura\UserPolicyBundle\Contracts\Policy
接口。 - 使用
#[AsPolicy]
属性将策略关联到主题。 - 使用
/** @implements Policy<InstanceClass, SubjectClass> */
注释策略以帮助静态分析/自动完成 - 策略方法可以具有任意名称,即它们不受 CRUD 操作的限制。
canDo
函数始终接收使用该特质的对象作为第一个参数,接着是能力(如create
、update
等),主题(作为类字符串或对象实例)以及传递给can
函数的任何其他参数
由于 canDo
函数必须始终返回 Granted
或 Rejected
的实例,因此上面示例中的默认匹配臂始终返回一个 AbilityNotSupported
"拒绝"。我们建议您也这样做。
灵感
- Laravel 授权 (https://laravel.net.cn/docs/authorization)
- GeekCell 用户策略包 (https://github.com/geekcell/user-policy-bundle)