ACL与域对象和数据映射器?

在阅读Matthew Weier O'Phinney关于在模型中实现ACL的许多帖子后,我一直专注于做这件事的最佳方式。 但是,在进一步研究域对象的最佳实践后,我明白这些模型不应包含对数据映射器或任何CRUD操作的任何引用。

以ERM软件为例,该软件可以维护库存并根据销售和采购订单处理/运输公司的货物。 我想有几个域名...

  • 公司
  • 装船
  • 订购
  • 产品
  • 部件
  • 还有其他一些
  • 由于公司可以有不同的类型(例如制造商,供应商,零售商),因此该信息存储在我的数据库中的许多表格中(例如公司,类型,公司类型)。 因此,我有一个公司域的数据映射器,它使用对象作为每个数据库表的Zend_Db_Table实例。

    在我的控制器操作中,我明白应该只有很少的逻辑。 例如,创建一家新公司可能会像这样...

    public function createAction()
    {
      // Receive JSON request from front end
      $data = Zend_Json::decode($request);
      $companyObj = new App_Model_Company();
      $companyObj->populate($data);
      $companyMapper = new App_Model_DataMapper_Company();
      $companyMapper->save($companyObj);
    }
    

    考虑到这一点,我认为最好将我的ACL检查合并到DataMapper中,并将Validation合并到Domain Object中。 我的域对象都从一个基本的抽象类扩展__set ,它重载了PHP的魔术__set__get方法。 在每个域对象的构造函数中,我通过用键填充$_properties数组来定义对象的属性。 这样,我的__set方法看起来像...

    public function __set($property, $value)
    {
    
        $className = __CLASS__;
        if(!array_key_exists($property, $this->_properties))
        {
            throw new Zend_Exception("Class [ $className ] has no property [ $property ]");
        }
    
        // @return Zend_Form
        $validator = $this->getValidator();
    
        /*
         * Validate provided $value against Zend_Form element $property
         */
    
        $this->properties[$property] = $value;
        }
    }
    

    我所有的数据映射器的save()方法都提示App_Model_DomainObjectAbstract $obj

    问题#1 - 由于我的数据映射器将处理所有CRUD操作,并且域对象应该只包含特定于该域的属性,所以我觉得ACL检查属于数据映射器 - 这是否可以接受?

    我试图避免在我的控制器中实例化数据映射器,但是现在我认为我对这种设计模式有了更好的理解,这似乎不合理。

    问题2 - 我是否在这个过程中复杂化了,我是否应该编写一个扩展Zend_Controller_Plugin_Abstract的ACL插件,并根据preDispatch()方法中的传入请求处理ACL?

    非常感谢您的宝贵时间!


    这里有一个共识(仔细阅读@teresko的答案) ACLs最适合Decorator模式 (安全容器)。

    如果您的ACL的权限定义存储在数据库中,那么您必须有一个DataMapper来映射数据库上的acl定义与Zend_Acl对象的实际实现及其resourcesrolesprivileges

    由于ZF 1本质 (很多反模式,全局状态等),可能你不会实现控制器装饰器。 相反,您将使用一个插件(preDispatch)来为您检查它。 所以你的ACL必须是初始化的第一个对象之一。

    考虑到您的ACL定义基于controlleraction名称,您的插件将调用您的AclMapper来获取已填充的ACL对象,然后检查是否允许当前用户访问给定资源。

    检查此示例代码:

    class Admin_Plugin_AccessCheck extends Zend_Controller_Plugin_Abstract 
    {
        public function preDispatch(Zend_Controller_Request_Abstract $request)
        {
            if($request->getModuleName() != 'admin')
            {
                return;
            }
    
    
            $auth = Zend_Auth::getInstance();
            $action = null;
    
            if(!$auth->hasIdentity())
            {
               $action = 'login'; 
            }
            else
            {
                /**
                 * Note that this is not a good practice (singletons). 
                 * But in this case it's avoiding re-loading the Acl from database
                 *  every time you need it. Also, considering that ZF 1 is full of 
                 * singletons, it'll not hurt, I think ;)
                 * YOU CAN CHANGE THIS LINE TO $aclMapper->getAcl();
                 */
    
                $acl = Acl::getInstance();
    
                $resource = $request->getModuleName() . ':' . $request->getControllerName();
                $privilege = $request->getActionName();
    
                $identity = $auth->getStorage()->read();
                $role = $identity->role_id;
    
                if($acl->has($resource))
                {
                    if(!$acl->isAllowed($role,$resource,$privilege))
                    {
                        $action = 'access-denied';
                    }
                }
            }
    
            if($action)
            {
                $request->setControllerName('authentication')
                        ->setActionName($action)
                        ->setModuleName('admin');
            }
        }
    }
    

    @问题#1:不,ACL不属于你的映射器。 记住关注点的分离。 如果你决定以每个对象为基础建立你的ACL,上面链接的装饰者方法是你的方法。 然而,装饰器可能很好地在映射器周围实现。 考虑这个由ZF1提供的acl结构:resource:你的域实体,例如classname role:用户角色特权:CRUD

    <?php
    class SecurityContainer {
        /**@var Zend_Acl*/
        protected $acl;
    
        /**@var DataMapper */
        protected $mapper;
    
        /**@var User|rolename*/
        protected $user;
    
        public function __construct($acl, $mapper, $user) {
            $this->acl = $acl;
            $this->mapper = $mapper;
            $this->user = $user;
        }
    
        public function __call($method, $entity) {
            if (method_exists($this->mapper, $method) {
                if ($this->acl->isAllowed($user, get_class($entity), $method) {
                    $this->mapper->$method($entity);
            }
        }
    }
    

    这会导致问题2:这实际上取决于您如何设计应用程序界面。 如果每个实体类型的每个CRUD操作都有一个动作,那么您可以通过FrontController-Plugin简单地实现您的acl,因为许多ZF1教程都会显示给您。 如果您需要一个更加细化的ACL,比如说,角色GUEST可能会更新公司名称,但管理人员可能会更新整个实体,或者如果您有多个实体发生更改的操作,则基于实体的方法是更好的一个imo。

    关于您概述的设计的一些其他想法:我不认为让实体验证自己是一个好主意。 尝试实现一个解决方案,其中一个类型由具体的验证器验证。 你甚至可以再次使用装饰器;)这仍然是一个更干净的解决方案。

    有理由不应该在控制器内使用你的映射器。 其中之一是做与您的数据库解耦的验收测试变得更加困难(但取决于您的实施情况)。 你指出另一个:保持你的行动尽可能短。 使用ACL和验证程序,您的操作将变得更大。 考虑在另一个问题中陈述的@teresko实现Servicelayer。 这对基于属性的ACL也是有帮助的,如果这是您需要的。

    希望能以某种方式帮助你。

    链接地址: http://www.djcxy.com/p/56233.html

    上一篇: ACL with Domain Object and Data Mapper?

    下一篇: Zend Framework Model Design