【问题标题】:Where to implement Zend_ACL with Domain Object and Data Mapper?在哪里使用域对象和数据映射器实现 Zend_ACL?
【发布时间】:2012-08-19 07:59:38
【问题描述】:

阅读了 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 中,并将验证合并到域对象中。 My Domain 对象都扩展了一个基本抽象类,它重载了 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 并根据 preDispatch() 方法中的传入请求处理 ACL 的 ACL 插件? p>

非常感谢您的宝贵时间!

【问题讨论】:

    标签: zend-framework design-patterns datamapper domain-model zend-acl


    【解决方案1】:

    大家一致认为here (carefully read the answer of @teresko)ACLs 最适合装饰器模式(一种安全容器)。

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

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

    考虑到您的 ACL 定义基于 controller 和 action 名称,您的插件将调用您的 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 类处理从数据库构建 resources、roles 和 privileges —— 这一切都很好。我看到了如何使用@teresko 提出的装饰器模式来实现这一点。但是,我对如何将这种模式实现为插件感到有些困惑。我知道在preDispatch 方法中,我可以轻松地为当前的 Zend_Auth 实例构建一个 ACL 对象,但我对如何根据传入请求中包含的数据(JSON,来自 ExtJS)构建域对象感到困惑.
    • @Keyne - 这很有帮助,非常感谢!我最后关心的是如何让控制器操作可用于特定(即非 CRUD)任务,例如“获取库存产品”。我也许可以在客户端处理这个问题,因为 ExtJS 构建了可以过滤的数据存储。我要在后端构建这样的功能(我需要这样做以允许外部服务进行 API 调用),我可以将这样的请求视为 READ(就 ACL 而言)并使用方法在我的服务层中构建控制器操作的响应。这是正确的吗?
    • @JohnHall 不确定我是否正确...您是否也在提供 Web 服务,或者实际上您是否正在连接到第 3 部分 API?如果您的控制器上有任何不与数据库执行任何交互的操作,没有问题,您仍然可以将其添加到 ACL。无需在客户端进行。将其与 READ 权限联系起来看起来是正确的。 IIRC,如果您连接到 API,它也是一项服务。然后,你需要它的特权(或至少使它成为不安全的资源)。
    • @Keyne 这个后端将为我的前端(ExtJS 应用程序)提供服务,并为第 3 方使用提供 API。在任何一种情况下,如果我想允许对“缺货产品”的请求,我想我可以在我的请求中将其作为参数实现。例如,我的请求可能包括controller(资源)action(特权)和一些extra 参数,用于进一步过滤所请求的数据。因此,一个有效的请求可能针对Product 控制器、Read 操作和outOfStock 作为extra 参数。这看起来是一种有效/合法的方法吗?
    • 是的,它是有效的。就像 Twitter API 一样,即dev.twitter.com/docs/api/1/get/search你走在正确的道路上。
    【解决方案2】:

    @问题 #1: 不,ACL 不属于您的映射器。牢记关注点分离。如果您决定将 ACL 建立在基于每个对象的基础上,那么上面链接的装饰器方法就是您要走的路。但是,装饰器可以很好地围绕映射器实现。 考虑 ZF1 提供的这个 acl 结构: 资源:您的域实体,例如类名 角色:用户角色 特权:C-R-U-D

    <?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 可能会更新公司名称,但经理可能会更新整个实体,或者如果您有更改多个实体的操作,则基于实体的方法是更好的一个海事组织。

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

    您不应该在控制器操作中使用映射器是有原因的。一是做与数据库分离的验收测试变得更加困难(不过取决于您的实现)。你指出了另一个:让你的动作尽可能短。使用 ACL 和验证器,您的操作将变得更大。考虑按照另一个问题中所述的@teresko 实施服务层。如果您需要,这对于基于属性的 ACL 也很有帮助。

    希望对你有所帮助。

    【讨论】:

    • +1 谢谢!这对我来说很有意义,但是正如我在回复@Keyne 的评论中所描述的那样,我对如何在前端控制器插件中构建$entity 感到困惑。例如,如果我正在尝试create 一家新公司,preDispatch 方法是否应该包含解码传入 JSON 请求的逻辑和 _populate() 的属性,然后将其发送到 new App_Model_Company() 对象new App_Model_Company() ?我觉得我一定错过了一些非常大的东西,因为我刚才描述的内容会导致preDispatch 方法中出现大量条件代码。
    • 实际上,您遗漏了一些非常小的东西。如果您打算将您的 acl 基于请求,请使用 predispatch-plugin,这意味着允许用户角色访问某些控制器(资源)上的某些操作(特权)。如果以上内容对您来说还不够,请使用每个装饰器(装饰您的映射器/服务)基于实体的 ACl。现在事情变得更容易理解了吗?否则我今晚会尝试用更多代码更新我的答案
    • 啊啊啊!再次感谢你!我试图将这两种方法合二为一,如果我理解正确,它们实际上是两种不同的方法,应该这样对待。我的应用程序没有复杂的权限; role 将有权访问 resource 上的 privilege,而不是该资源的单个属性。尽管如此,我对如何避免在我的控制器操作中使用域的 DataMapper 感到困惑。我的saveAction 如何在不先构建域模型,然后将该模型发送到 DataMapper 的保存方法的情况下写入数据库?
    • @Jojo 差不多我认为,正如您可能已经注意到的,Teresko 的答案是装饰控制器,它将处理每个请求。在 Zend Framework 1 上将控制器的装饰器实现为安全外壳的主要问题是,您需要调整框架来实现它,因为在实际架构下装饰控制器并不容易(也许在 ZF 2 上是可能的)。还有第二种选择,如果你有的话,那就是让 ACL 检查你的服务层。因此,您将使用 ACL 装饰您的服务。总而言之,如果您使用的是 ZF 1,该插件将非常适合。
    猜你喜欢
    • 2014-06-09
    • 2011-10-22
    • 2011-04-13
    • 1970-01-01
    • 2016-10-10
    • 2012-08-14
    • 2010-12-30
    • 1970-01-01
    • 2017-07-11
    相关资源
    最近更新 更多