6

Gần đây, chúng tôi đã bắt đầu sử dụng Doctrine 2.2 và các phần của Zend Framework 2 trong nỗ lực cải thiện tổ chức, giảm trùng lặp, trong số những thứ khác. Hôm nay, tôi bắt đầu vứt bỏ các ý tưởng để thực hiện một lớp dịch vụ để hoạt động như một trung gian giữa các bộ điều khiển và các thực thể Doctrine.Truy cập dữ liệu và bảo mật trong lớp dịch vụ (Doctrine & ZF)

Hiện tại, phần lớn logic của chúng tôi nằm trong bộ điều khiển. Ngoài ra, chúng tôi sử dụng trình trợ giúp hành động để kiểm tra các quyền nhất định; tuy nhiên, tôi đã đưa ra một cách tiếp cận mới sau khi triển khai Zend \ Di. Tôi bắt đầu tạo các mô hình dịch vụ cụ thể cho thực thể, sử dụng Zend \ Di để tiêm một cá thể EntityManager và các quyền của người dùng hiện tại.

Mã điều khiển như sau:

class Project_DeleteController extends Webjawns_Controller_Action 
{ 
    public function init() 
    { 
     $this->_initJsonContext(); 
    } 

    public function indexAction() 
    { 
     $response = $this->_getAjaxResponse(); 

     $auditId = (int) $this->_getParam('audit_id'); 
     if (!$auditId) { 
      throw new DomainException('Audit ID required'); 
     } 

     /* @var $auditService Service\Audit */ 
     $auditService = $this->getDependencyInjector()->get('Service\Audit'); 

     try { 
      $auditService->delete($auditId); 
      $response->setStatusSuccess(); 
     } catch (Webjawns\Exception\SecurityException $e) { 
      $this->_noAuth(); 
     } catch (Webjawns\Exception\Exception $e) { 
      $response->setStatusFailure($e->getMessage()); 
     } 

     $response->sendResponse(); 
    } 
} 

Và một ví dụ về một trong các lớp dịch vụ của chúng tôi. Hàm khởi tạo nhận hai tham số - một tham số nhận EntityManager và đối tượng Entity \ UserAccess khác - được Zend \ Di tiêm.

namespace Service; 

use Webjawns\Service\Doctrine, 
    Webjawns\Exception; 

class Audit extends AbstractService 
{ 
    public function delete($auditId) 
    { 
     // Only account admins can delete audits 
     if (\Webjawns_Acl::ROLE_ACCT_ADMIN != $this->getUserAccess()->getAccessRole()) { 
      throw new Exception\SecurityException('Only account administrators can delete audits'); 
     } 

     $audit = $this->get($auditId); 

     if ($audit->getAuditStatus() !== \Entity\Audit::STATUS_IN_PROGRESS) { 
      throw new Exception\DomainException('Audits cannot be deleted once submitted for review'); 
     } 

     $em = $this->getEntityManager(); 
     $em->remove($audit); 
     $em->flush(); 
    } 

    /** 
    * @param integer $auditId 
    * @return \Entity\Audit 
    */ 
    public function get($auditId) 
    { 
     /* @var $audit \Entity\Audit */ 
     $audit = $this->getEntityManager()->find('Entity\Audit', $auditId); 
     if (null === $audit) { 
      throw new Exception\DomainException('Audit not found'); 
     } 

     if ($audit->getAccount()->getAccountId() != $this->getUserAccess()->getAccount()->getAccountId()) { 
      throw new Exception\SecurityException('User and audit accounts do not match'); 
     } 

     return $audit; 
    } 
} 
  1. Đây có phải là một mô hình thích hợp để sử dụng cho những gì chúng tôi đang cố gắng để thực hiện?
  2. Thực tiễn tốt là có xác thực quyền trong lớp dịch vụ như được đăng?
  3. Khi tôi hiểu nó, xem logic vẫn nằm trong bộ điều khiển, cho phép mô hình linh hoạt được sử dụng trong các ngữ cảnh khác nhau (JSON, XML, HTML, v.v.). Suy nghĩ?

Tôi hài lòng với cách này hoạt động cho đến nay, nhưng nếu có ai thấy bất kỳ nhược điểm nào về cách chúng tôi đang thực hiện việc này, vui lòng đăng ý kiến ​​của bạn.

+1

Chỉ cần tôi hai xu trên xác thực.Tôi không tin rằng có một cách đúng và một cách sai, tuy nhiên, tôi bắt đầu đưa xác thực của tôi vào lớp dịch vụ, nhưng sau đó chuyển nó vào bộ điều khiển của tôi. Lý do của tôi là lớp dịch vụ là API nội bộ của tôi và tôi nên sử dụng lớp điều khiển của mình để lộ ra thế giới đó, vì vậy nó nên quyết định ai sẽ truy cập vào cái gì. Ngoài ra, nếu tôi muốn xây dựng bất kỳ công cụ/tập lệnh nội bộ nào, tôi không cần phải xây dựng xác thực vào chúng để sử dụng lớp dịch vụ của tôi. –

+0

Cẩn thận không trộn lẫn xác thực và kiểm soát truy cập. Xác thực có thể (nên?) Đi vào mô-đun, trước khi bất kỳ lớp miền được đưa vào hình ảnh. Chỉ khi bạn đã thiết lập danh tính người dùng. Ví dụ: if (! $ AuthService-> hasIdentity()) bên trong mô hình dịch vụ miền của bạn. – dualmon

+1

@JamieSutherland: Tôi không đồng ý. Các dịch vụ xác định logic nghiệp vụ; bộ điều khiển là cầu nối giữa các yêu cầu và logic nghiệp vụ thích hợp. Ví dụ: bạn sẽ chỉ có một dịch vụ duy nhất để đặt hàng một sản phẩm, nhưng bạn có thể có nhiều bộ điều khiển cho các yêu cầu HTTP, yêu cầu API, v.v. Nếu bạn quan tâm đến ACL đang yêu cầu cụ thể (ví dụ, thông qua HTTP, bạn có thể mong đợi một phiên người dùng mà bạn mong đợi một khóa bí mật cho các yêu cầu API), hãy tổng quát việc triển khai ACL của bạn để cho phép điều này. – moteutsch

Trả lời

1

Tôi thích những gì bạn đang làm ở đây và tôi cho rằng sự tách biệt mối quan tâm của bạn là tốt. Chúng tôi đang thử nghiệm với việc tiến thêm một bước nữa, sử dụng kho lưu trữ tùy chỉnh. Vì vậy, ví dụ, nơi một phương pháp mô hình/dịch vụ tiêu chuẩn có thể trông như thế này:

public function findAll($sort = null) 
{ 
    if (!$sort) $sort = array('name' => 'asc'); 
    return $this->getEm()->getRepository('Application\Entity\PartType') 
       ->findAll($sort); 

} 

... chúng tôi có thêm việc mà cần phải DQL vào kho, để giữ tất cả DQL ra khỏi mô hình, ví dụ:

public function findAllProducts($sort = null) 
{ 
    if (!$sort) $sort = array('name' => 'asc'); 
    return $this->getEm()->getRepository('Application\Entity\PartType') 
       ->findAllProducts($sort); 

} 

Đối với mô hình trên, lớp kho trông như thế này:

<?php 
namespace Application\Repository; 

use Application\Entity\PartType; 
use Doctrine\ORM\EntityRepository; 

class PartTypeRepository extends EntityRepository 
{ 

    public function findAllProducts($order=NULL) 
    { 
     return $this->_em->createQuery(
        "SELECT p FROM Application\Entity\PartType p 
         WHERE p.productGroup IS NOT NULL 
         ORDER BY p.name" 
       )->getResult(); 
    } 

} 

Lưu ý rằng chúng tôi đã chỉ đơn giản là mở rộng học thuyết \ ORM \ EntityRepository có nghĩa là chúng ta không cần phải tái xác định tất cả các các phương thức kho lưu trữ Doctrine chuẩn, nhưng chúng ta có thể ghi đè chúng nếu cần, và chúng ta có thể thêm các tùy chỉnh riêng của chúng ta.

Vì vậy, liên quan đến kiểm soát truy cập, nó cho chúng ta khả năng thêm ràng buộc dựa trên nhận dạng hoặc các điều kiện cấp bản ghi khác ở mức rất thấp, bằng cách truy cập logic nghiệp vụ trong các dịch vụ của bạn từ Kho lưu trữ. Bằng cách làm theo cách này, các dịch vụ không biết về việc triển khai. Miễn là chúng tôi nghiêm túc về việc không đưa DQL vào các phần khác của ứng dụng, chúng tôi có thể đạt được các hạn chế kinh doanh cấp kỷ lục cho bất kỳ lớp nào truy cập cơ sở dữ liệu thông qua kho lưu trữ. (Xem ra cho DQL tùy chỉnh ở cấp độ cao hơn của ứng dụng).

Ví dụ:

public function findAll($order=NULL) 
    { 
     // assumes PHP 5.4 for trait to reduce boilerplate locator code 
     use authService; 

     if($this->hasIdentity()) { 
      return $this->_em->createQuery(
         "SELECT p FROM Application\Entity\PartType p 
          JOIN p.assignments a 
          WHERE a.id = " . $this->getIdentity()->getId() 
        )->getResult(); 
     } else { 
      return NULL; 
     } 
    } 
+1

Tôi mạnh mẽ không đồng ý với việc đặt mã kiểm soát truy cập trong kho lưu trữ. Kho lưu trữ không nên biết gì về logic nghiệp vụ của ứng dụng. Nó chỉ nên quan tâm đến việc lấy dữ liệu. Logic nghiệp vụ là dành cho các lớp và dịch vụ cấp cao hơn. – moteutsch

+0

Tôi nghĩ rằng nó cũng là một ý tưởng hay để trừu tượng hóa các kho lưu trữ từ việc thực hiện Doctrine. Sử dụng thành phần (của các lớp kho lưu trữ Doctrine mặc định) thay vì thừa kế. Điều này kết hợp với mã hóa cho một giao diện cho phép bạn dễ dàng trao đổi các nguồn dữ liệu trong một số hoặc tất cả các kho lưu trữ của bạn (những gì tôi muốn gọi là "những người lập bản đồ", để tránh nhầm lẫn với kho Doctrine). – moteutsch

Các vấn đề liên quan