1

在閱讀Matthew Weier O'Phinney關於在模型中實現ACL的許多帖子後,我一直專注於做這件事的最佳方式。但是,在進一步研究域對象的最佳實踐後,我明白這些模型不應包含對數據映射器或任何CRUD操作的任何引用。在哪裏實施Zend_ACL與域對象和數據映射器?

以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和驗證到域對象。我的域對象全部擴展了基本的抽象類,它重載了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操作,並且域對象應該只包含特定於該域的屬性,所以我覺得ACL檢查屬於數據映射器 - 這可以接受嗎?

我試圖避免實例數據映射在我的控制器,但是這似乎不合理的,現在,我想有一個更好的理解這種設計模式。

問題#2 - 我是在這個過程複雜,我應該代替編寫基於preDispatch()方法傳入的請求延長Zend_Controller_Plugin_Abstract和處理ACL訪問控制列表插件?

非常感謝您的時間!

回答

2

有一個共識here (carefully read the answer of @teresko)ACLs將最適合裝飾模式(安全容器)。

如果ACL的權限定義存儲在數據庫中,那麼你必須有一個DataMapper的到你的數據庫上acl定義並以其resourcesrolesprivileges實際執行Zend_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'); 
     } 
    } 
} 
+0

+1供參考@tereskos解釋。 – Jojo

+0

+1感謝您的參考!這對我來說很有意義。我目前的ACL類處理從數據庫中建立'resources','roles'和'privileges' - 這一切都很好。我看到我可以用@teresko提出的Decorator模式來實現這個功能。但是,我對於如何實現這種模式作爲插件略有困惑。我明白,在'preDispatch'方法中,我可以輕鬆地爲當前的Zend_Auth實例構建一個ACL對象,但我對如何基於傳入請求中包含的數據構建域對象感到困惑(JSON,來自ExtJS) 。 –

+0

@JohnHall讓我知道更新的答案是否有幫助。 –

1

@Question#1: 沒有,ACL沒有的映射器裏面的歸屬。記住關注點的分離。如果你決定以每個對象爲基礎建立你的ACL,上面鏈接的裝飾者方法是你的方法。然而,裝飾器可能很好地在映射器周圍實現。 考慮這個ACL結構由ZF1提供: 資源:你的域實體,如類名 角色:用戶角色 特權: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。

有關您所設計設計的一些其他想法: 我不認爲讓實體驗證自己是一個好主意。嘗試實現一個解決方案,其中一個類型由具體的驗證器驗證。你甚至可以再次使用裝飾器;)這仍然是一個更乾淨的解決方案。

有理由不應該在你的controller-actions中使用你的映射器。其中之一是做與您的數據庫解耦的驗收測試變得更加困難(但取決於您的實施情況)。你指出另一個:保持你的行動儘可能短。使用ACL和驗證程序,您的操作將變得更大。考慮在另一個問題中陳述的@teresko實現Servicelayer。這對基於屬性的ACL也是有幫助的,如果這是您需要的。

希望能以某種方式幫助你。

+0

+1謝謝!這對我來說很有意義,但是正如我在對@Keyne的迴應中所描述的那樣,我很困惑我將如何在前端控制器插件中構建'$ entity'。例如,如果我試圖「創建」一家新公司,那麼preDispatch方法應該在發送之前包含解碼傳入JSON請求和'_populate()'新App_Model_Company()'屬性的邏輯的邏輯'SecurityContainer'對象?我覺得我必須錯過一些相當巨大的東西,正如我剛纔所描述的那樣會導致'preDispatch'方法中的大量條件代碼。 –

+0

其實你錯過了一些非常小的東西。如果您要將acl基於請求,請使用predispatch-plugin,這意味着允許用戶角色訪問特定控制器(資源)上的某些操作(權限)。如果以上不足以滿足您的需求,請使用基於實體的裝飾器AC1(裝飾您的映射器/服務)。現在事情更容易理解嗎?否則,我嘗試用今天晚上的更多代碼更新我的答案 – Jojo

+0

Ahhhh!再次感謝你!我試圖將這兩種方法合併爲一種,如果我理解正確,他們實際上是兩種分開的方法,應該這樣對待。我的應用程序沒有複雜的權限;一個「角色」將有權訪問「資源」上的「特權」,而不是該資源的單個屬性。儘管如此,我很困惑如何避免在我的控制器操作中使用域的DataMapper。如果不先構建域模型,然後將該模型發送到DataMapper的保存方法,我的'saveAction'如何寫入數據庫? –