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



在阅读了Matthew Weier O'Phinney关于在模型中实现ACL的许多帖子后,我一直专注于实现这一点的最佳方法。然而,在对域对象的最佳实践进行进一步研究后,我理解这些模型不应包含对数据映射器或任何CRUD操作的任何引用。

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

  • 公司
  • 装运
  • 订单
  • 产品
  • 装配
  • 还有其他一些

由于公司可以有不同的类型(例如制造商、供应商、零售商),这些信息存储在我的数据库中的许多表中(例如公司、类型、company_Types)。因此,我的公司域有一个数据映射器,它为每个数据库表的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中,并将验证合并到Domain对象中。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()方法都typehint App_Model_DomainObjectAbstract $obj

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

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

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

非常感谢您抽出时间!

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

如果ACL的权限定义存储在数据库中,则必须有一个DataMapper来在数据库上的acl定义和具有resourcesrolesprivilegesZend_Acl对象的实际实现之间进行映射。

由于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建立在每个对象的基础上,那么上面链接的decorator方法就是您的选择。不过,装饰器可以很好地围绕映射器来实现。考虑ZF1提供的acl结构:resource:您的域实体,例如类名角色:用户角色特权: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插件来实现您的acl,正如ZF1的许多教程所展示的那样。如果你需要一个更细粒度的ACL,比如说,角色GUEST可能会更新一个公司名称,但经理可能会更新整个实体,或者如果你有多个实体被更改的操作,基于实体的方法是更好的方法。

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

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

希望这对你有所帮助。

相关内容

  • 没有找到相关文章

最新更新