Динамическое добавление роли пользователю

Мы используем функцию ролей Symfony2, чтобы ограничить доступ пользователей к определенным частям нашего приложения. Пользователи могут приобретать годовые подписки, и у каждой из наших сущностей User есть много сущностей Subscription, у которых есть дата начала и окончания.

Теперь, есть ли способ динамически добавить роль пользователю, основываясь на том, есть ли у него "активная" подписка? В рельсах я бы просто позволил модели справиться с тем, обладает ли она необходимыми правами, но я знаю, что по замыслу сущности symfony2 не должны иметь доступа к Doctrine.

Я знаю, что вы можете получить доступ к ассоциациям сущностей из экземпляра сущности, но это будет проходить через все объекты пользовательской подписки, и это мне кажется излишне громоздким.

Ответы

Ответ 1

Думаю, вам лучше настроить пользовательский избиратель и атрибут.

/**
 * @Route("/whatever/")
 * @Template
 * @Secure("SUBSCRIPTION_X")
 */
public function viewAction()
{
    // etc...
}

Роль SUBSCRIPTION_X (ака-атрибут) должна обрабатываться пользовательским классом избирателя.

class SubscriptionVoter implements VoterInterface
{
    private $em;

    public function __construct($em)
    {
        $this->em = $em;
    }

    public function supportsAttribute($attribute)
    {
        return 0 === strpos($attribute, 'SUBSCRIPTION_');
    }

    public function supportsClass($class)
    {
        return true;
    }

    public function vote(TokenInterface $token, $object, array $attributes)
    {
        // run your query and return either...
        //  * VoterInterface::ACCESS_GRANTED
        //  * VoterInterface::ACCESS_ABSTAIN
        //  * VoterInterface::ACCESS_DENIED
    }
}

Вам нужно будет настроить и пометить своего избирателя:

services:
    subscription_voter:
        class: SubscriptionVoter
        public: false
        arguments: [ @doctrine.orm.entity_manager ]
        tags:
            - { name: security.voter }

Ответ 2

Предполагая, что у вас есть правильное отношение "подписки" в вашем пользовательском объекте.

Возможно, вы можете попробовать что-то вроде:

public function getRoles()
{
    $todayDate = new DateTime();
    $activesSubscriptions = $this->subscriptions->filter(function($entity) use ($todayDate) {
        return (($todayDate >= $entity->dateBegin()) && ($todayDate < $entity->dateEnd()));
    });

    if (!isEmpty($activesSubscriptions)) {
        return array('ROLE_OK');
    }

    return array('ROLE_KO');
}

Изменение роли может быть выполнено с помощью

$sc = $this->get('security.context')
$user = $sc->getToken()->getUser();
$user->setRole('ROLE_NEW');
// Assuming that "main" is your firewall name :
$token = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($user, null, 'main', $user->getRoles());
$sc->setToken($token);

Но после изменения страницы вызывается функция refreshUser провайдера и иногда, так как это имеет место с EntityUserProvider, роль перезаписывается запросом. Чтобы избежать этого, вам нужен пользовательский поставщик.