Как реализовать сложную логику проверки привилегий пользователя в Zend Framework 2?

Работаю над проектом на Zend Framework 2.4 с Doctrine ORM и модулем BjyAuthorize. Встал вопрос как определить разрешено ли данному пользователю данное действие над данным ресурсом. Простого объявления ролей и их привилегий в конфиге, как описывают в примерах, получается недостаточно. Попытался использовать Assertions по примерам из документации. Написал фабрику, которая создает экземпляр Assertion и передает ему доктриновский EntityManager (чтобы в assert-е сделать запрос к БД и проверить нужные данные). Код для пояснения:

Фабрика:
<?php
namespace Foo\Permissions\Acl\Assertion;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Foo\Permissions\Acl\Assertion\IsUserDrunkAssertion;

class IsUserDrunkAssertionFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $em = $serviceLocator->get('doctrine.entitymanager.orm_default');
        $assertion = new IsUserDrunkAssertion();
        $assertion->setObjectManager($em);
        return $assertion;
    }
}

Assertion:
<?php
namespace Foo\Permissions\Acl\Assertion;

use Zend\Permissions\Acl\Assertion\AssertionInterface;
use Doctrine\Common\Persistence\ObjectManager;
// ...

class IsUserDrunkAssertion implements AssertionInterface
{
    protected $objectManager;

    public function setObjectManager(ObjectManager $objectManager)
    {
        $this->objectManager = $objectManager;
        return $this;
    }

    public function getObjectManager()
    {
        return $this->objectManager;
    }

    public function assert(Acl $acl, RoleInterface $role = null, ResourceInterface $resource = null, $privilege = null)
    {
        $decision = // ... здесь бы запрос сделать
        return $decision;
    }
}

Однако, такой подход приводит к ошибке кэша:
Fatal error: Uncaught exception 'PDOException' with message 
'You cannot serialize or unserialize PDO instances' in 
/var/www/vendor/zendframework/zendframework/library/Zend/Serializer/Adapter/PhpSerialize.php:48 

Stack trace: 
#0 [internal function]: PDO->__sleep() 
#1 /var/www/vendor/zendframework/zendframework/library/Zend/Serializer/Adapter/PhpSerialize.php(48): serialize(Object(Zend\Permissions\Acl\Acl)) 
#2 /var/www/vendor/zendframework/zendframework/library/Zend/Cache/Storage/Plugin/Serializer.php(102): Zend\Serializer\Adapter\PhpSerialize->serialize(Object(Zend\Permissions\Acl\Acl)) 
#3 [internal function]: Zend\Cache\Storage\Plugin\Serializer->onWriteItemPre(Object(Zend\Cache\Storage\Event)) 
#4 /var/www/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php(444): call_user_func(Array, Object(Zend\Cache\Storage\Event)) 
#5 /var/www/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php(205): Zend\EventManager\EventManager->triggerListeners('setItem.pre', Object(Zend\Cache\Stor in /var/www/vendor/zendframework/zendframework/library/Zend/Serializer/Adapter/PhpSerialize.php on line 48

Получается из assertion-а к БД никак не обратиться? Надо делать запросы в каком-то другом месте? Или вообще изменить подход к реализации проверок?
  • Вопрос задан
  • 861 просмотр
Решения вопроса 2
Fesor
@Fesor
Full-stack developer (Symfony, Angular)
Курить в сторону Security Voters. Если подобное не поддерживается зэндом из коробки, можно быстро наваять свою реализацию или взять какую-либо готовую имплментацию. Удобнее способа разруливать права на выполнения действий я лично пока не встречал.
Ответ написан
27cm
@27cm
TODO: Написать статус
Zend\Permissions\Acl не предполагает использование базы данных (см. framework.zend.com/manual/current/en/modules/zend....).

Получается из assertion-а к БД никак не обратиться?

Можно было бы и там оставить, но тогда придётся отключить кеширование, да вот беда - BjyAuthorize такой возможности не предоставляет. Описать им проблему в issue не хотите?

Надо делать запросы в каком-то другом месте?

Если хотите оставить кеширование, то придётся делать в каком-то другом месте.

Могут предложить "грязное" / временное решение проблемы, за которое мне будет стыдно, но всё же.
Не хранить ObjectManager внутри IsUserDrunkAssertion, а получать его через статический метод другого класса:
class IsUserDrunkAssertionFactory implements FactoryInterface
{
    static protected $objectManager;

    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        static::$objectManager = $serviceLocator->get('doctrine.entitymanager.orm_default');
        $assertion = new IsUserDrunkAssertion();
        return $assertion;
    }

    static public function getObjectManager()
    {
        return static::$objectManager;
    } 
}

class IsUserDrunkAssertion implements AssertionInterface
{
    public function assert(Acl $acl, RoleInterface $role = null, ResourceInterface $resource = null, $privilege = null)
    {
        $objectManager = IsUserDrunkAssertionFactory::getObjectManager();
        $decision = // ... здесь бы запрос сделать
        return $decision;
    }
}
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы