如何在事件订阅器中获取投‘granted’票的Voter?相关实现咨询
如何在事件订阅器中获取投“granted”票的Voter
嘿,我来帮你搞定这个需求!Symfony本身并没有直接提供一个现成的服务来获取所有Voter的投票结果,但我们可以通过监听投票相关事件来完美实现你的场景——完全不用在Voter内部处理消息或分发事件,正好符合你想规避那些方案的要求。
实现思路
我们分两步走:
- 监听每个Voter的投票事件,记录下每个Voter的投票结果(是允许还是拒绝);
- 在整个授权流程结束后,检查最终的授权结果,再结合之前记录的投票情况,判断是否符合「主Voter拒绝、管理员Voter允许」的场景,然后触发提示消息。
1. 创建投票结果存储服务
首先我们需要一个临时存储当前请求中所有Voter投票结果的服务,因为每个请求是独立的,所以这个服务要设置为请求作用域(保证每个请求的记录互不干扰):
// src/Security/VoteResultStorage.php namespace App\Security; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; class VoteResultStorage { private array $votes = []; // 记录单个Voter的投票结果 public function recordVote(VoterInterface $voter, int $vote): void { $this->votes[get_class($voter)] = $vote; } // 获取所有Voter的投票记录 public function getVotes(): array { return $this->votes; } // 请求结束后重置记录,避免影响下一个请求 public function reset(): void { $this->votes = []; } }
然后在services.yaml里配置这个服务为请求作用域:
# config/services.yaml services: App\Security\VoteResultStorage: scope: request autowire: true autoconfigure: true
2. 监听VoteEvent记录投票结果
Symfony的VoteEvent会在每个Voter完成投票后立即触发,我们可以创建一个事件订阅器来监听这个事件,把每个Voter的投票结果存入刚才的存储服务:
// src/EventSubscriber/VoteRecordingSubscriber.php namespace App\EventSubscriber; use App\Security\VoteResultStorage; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Core\Event\VoteEvent; class VoteRecordingSubscriber implements EventSubscriberInterface { public function __construct(private VoteResultStorage $voteResultStorage) { } // 声明要监听的事件 public static function getSubscribedEvents(): array { return [ VoteEvent::class => 'onVote', ]; } // 处理VoteEvent,记录投票结果 public function onVote(VoteEvent $event): void { $this->voteResultStorage->recordVote( $event->getVoter(), $event->getVote() ); } }
3. 监听AuthorizationCheckEvent判断业务场景
接下来监听AuthorizationCheckEvent——这个事件会在整个授权流程完全结束后触发。在这里我们可以检查最终的授权结果,再结合存储的投票记录,判断是否符合你的业务需求:
// src/EventSubscriber/AuthorizationResultSubscriber.php namespace App\EventSubscriber; use App\Security\AdminVoter; // 替换成你实际的管理员Voter类 use App\Security\MainVoter; // 替换成你实际的主Voter类 use App\Security\VoteResultStorage; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; use Symfony\Component\Security\Core\Event\AuthorizationCheckEvent; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; class AuthorizationResultSubscriber implements EventSubscriberInterface { public function __construct( private VoteResultStorage $voteResultStorage, private FlashBagInterface $flashBag ) { } public static function getSubscribedEvents(): array { return [ AuthorizationCheckEvent::class => 'onAuthorizationCheck', ]; } public function onAuthorizationCheck(AuthorizationCheckEvent $event): void { // 只有当最终授权结果是允许时,才需要检查投票细节 if (!$event->getAuthorizationDecision()) { return; } $votes = $this->voteResultStorage->getVotes(); // 获取主Voter和管理员Voter的投票结果 $mainVoterVote = $votes[MainVoter::class] ?? null; $adminVoterVote = $votes[AdminVoter::class] ?? null; // 判断是否符合你的场景:主Voter拒绝,管理员Voter允许 if ( $mainVoterVote === VoterInterface::ACCESS_DENIED && $adminVoterVote === VoterInterface::ACCESS_GRANTED ) { // 添加你需要的提示消息(这里用flash消息举例,你可以换成其他方式) $this->flashBag->add('info', '您通过管理员权限获得了访问权限'); } // 重置投票记录,避免影响下一个请求 $this->voteResultStorage->reset(); } }
关键注意点
- 确保
VoteResultStorage是请求作用域,这样每个请求的投票记录都是独立的,不会互相干扰; - 替换代码中的
MainVoter和AdminVoter为你项目中实际的Voter类名; - 所有逻辑都集中在事件订阅器中,完全不需要修改Voter的代码,完美规避了你不想在Voter内处理的问题。
内容的提问来源于stack exchange,提问作者Tomasz Gąsior
相关产品推荐
相关产品推荐

