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
对象的实际实现及其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不属于你的映射器。 记住关注点的分离。 如果你决定以每个对象为基础建立你的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