在哪里用域对象和数据映射器实现Zend_ACL?

时间:2012-08-23 18:20:28

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

阅读了很多Matthew Weier O'Phinney关于在模型中实施ACL的帖子,我一直专注于实现这一目标的最佳方式。但是,在进一步研究Domain Objects的最佳实践后,我了解这些模型不应包含对Data Mappers或任何CRUD操作的任何引用。

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

  • 公司
  • 装运
  • 顺序
  • 产品
  • 装配
  • 还有其他几个

由于公司可以有不同的类型(例如制造商,供应商,零售商),因此该信息存储在我的数据库中的多个表中(例如公司,类型,公司类型)。因此,我有一个公司域的数据映射器,它使用每个数据库表的Zend_Db_Table实例的对象。

在我的Controller操作中,我知道应该有非常少的逻辑。例如,创建一个新公司可能会像这样......

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并验证到Domain Object中。 My Domain对象都扩展了一个基本的抽象类,它重载了PHP的神奇__set__get方法。在每个Domain Object的构造函数中,我通过用键填充$_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()方法都是typehint App_Model_DomainObjectAbstract $obj

问题#1 - 由于我的Data Mapper将处理所有CRUD操作,并且Domain对象应该只包含特定于该域的属性,我觉得ACL检查属于Data Mapper - 这是否可接受?< /强>

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

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

非常感谢你的时间!

2 个答案:

答案 0 :(得分:2)

ACLs达成共识ACL最适合装饰器模式(安全容器)。

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

  

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

考虑到您的ACL定义基于actionAclMapper名称,您的插件会调用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}}

答案 1 :(得分:1)

@Question#1: 不,ACL不属于您的映射器。记住关注点。如果您决定基于每个对象建立ACL,那么上面链接的装饰器方法就是您的选择。但是,装饰器可以很好地在映射器周围实现。 考虑ZF1提供的这个acl结构: resource:您的域实体,例如classname 角色:用户角色 特权: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可以更新公司名称,但是经理可以更新整个实体,或者如果您有更多一个实体被更改的操作,则基于实体的方法是一个更好的imo。

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

有理由不在控制器操作中使用映射器。一个是,进行与数据库分离的验收测试变得更加困难(取决于您的实现)。你指出另一个:保持你的行动尽可能短。使用ACL和验证器,您的操作将变得更大。考虑将Servicelayer实现为另一个问题中所述的@teresko。如果需要您的ACL,这对基于属性的ACL也很有帮助。

希望以某种方式帮助你。