6

최근 Doctrine 2.2 및 Zend Framework 2의 일부를 사용하여 조직을 개선하고 중복을 줄이기 시작했습니다. 오늘 저는 컨트롤러와 Doctrine 엔터티 사이의 중개자 역할을하는 서비스 계층을 구현하기위한 아이디어를 던지기 시작했습니다.서비스 레이어 (Doctrine & ZF)의 데이터 액세스 및 보안

지금 우리 논리의 대부분은 컨트롤러에 있습니다. 또한 액션 헬퍼를 사용하여 특정 권한을 테스트합니다. 그러나 Zend \ Di를 구현 한 후에 새로운 접근법을 생각해 냈습니다. Zend \ Di를 사용하여 EntityManager 인스턴스를 삽입하고 현재 사용자의 권한을 사용하는 엔티티 관련 서비스 모델을 만들기 시작했습니다.

제어기 코드는 다음과 같다 :

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(); 
    } 
} 

그리고 우리의 서비스 층의 하나의 예입니다. 생성자는 두 개의 매개 변수를 취합니다. 하나는 EntityManager이고 다른 하나는 Entity \ UserAccess 객체입니다. Zend \ Di에 의해 주입됩니다.

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. 이것은 우리가 달성하려고하는 무엇에 사용하는 적절한 패턴인가?
  2. 게시 된 서비스 계층 내에서 사용 권한 유효성 검사를 수행하는 것이 좋습니다.
  3. 필자가 이해 하듯이, 뷰 로직은 컨트롤러에 여전히 존재하므로 다양한 컨텍스트 (JSON, XML, HTML 등)에서 사용할 수있는 모델 유연성을 제공합니다. 생각?

저는 지금까지의 방식에 만족합니다. 그러나 누군가가 우리가 어떻게하는지에 대해 어떤 단점이 보이면, 생각해보십시오.

+1

그냥 내 두 펜스.올바른 방법과 잘못된 방법이 있다고 생각하지 않지만 서비스 계층에 인증을 시작했지만 내 컨트롤러로 옮겼습니다. 내 추론은 서비스 레이어가 내 내부 API이며 내 컨트롤러 레이어를 사용하여이를 세계에 공개해야하므로 누구에게 무엇에 대한 액세스 권한을 부여해야하는지 결정해야합니다. 또한 모든 내부 도구/스크립트 등을 빌드하려는 경우 내 서비스 계층을 사용하기 위해 인증을 구축 할 필요가 없습니다. –

+0

인증과 액세스 제어가 섞이지 않도록주의하십시오. 도메인 클래스가 그림으로 들어가기 전에 인증이 모듈로 들어가야합니까? 사용자 신원을 확인한 후에 만 예 : 도메인 서비스 모델 내부에서 if (! $ authService-> hasIdentity()). – dualmon

+1

@ JamieSutherland : 나는 동의하지 않는다. 서비스는 비즈니스 로직을 정의합니다. 컨트롤러는 요청과 적절한 비즈니스 로직 사이의 다리 역할을합니다. 예를 들어, 제품 주문을위한 단일 서비스 만 있지만 HTTP 요청, API 요청 등에 대한 여러 컨트롤러가있을 수 있습니다. 특정 요청을하는 ACL에 관심이 있다면 (예 : HTTP를 통해 API 요청의 비밀 키가 필요한 사용자 세션을 기대할 수 있음) ACL 구현을 일반화하여 허용하십시오. – moteutsch

답변

1

나는 여기서하고있는 일을 좋아하지 만, 나는 당신의 관심사 분리가 좋다고 생각합니다. 우리는 커스텀 리포지토리 (Custom Repositories)를 사용하여 한 걸음 더 나아가는 것을 시도하고 있습니다. 따라서, 표준 모델/서비스 방법은 다음과 같이 보일 수 있습니다 예를 들어

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

} 

... 우리가 저장소에 DQL을 필요로 일을 추가하고, 모델에서 모든 DQL을 유지하기 위해, 예를 들어 : 위의 모델에 대한

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

} 

은 저장소 클래스는 다음과 같습니다 : 우리는 단순히 교리를 확장 한

<?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(); 
    } 

} 

참고 \ ORM \ EntityRepository하는 우리 모두가 다시 정의 할 필요가 없습니다 것을 의미합니다 그만큼 표준 Doctrine 저장소 메쏘드가 필요하지만 필요에 따라 그것을 오버라이드 할 수 있으며, 우리는 자신 만의 메쏘드 저장소 메쏘드를 추가 할 수있다.

따라서 액세스 제어와 관련하여 리포지토리에서 서비스의 비즈니스 로직에 액세스하여 ID 기반 제약 조건이나 다른 레코드 수준 조건을 매우 낮은 수준으로 추가 할 수 있습니다. 이런 방식으로 서비스는 구현을 인식하지 못합니다. 앱의 다른 부분에 DQL을 두지 않는 한 엄격하게 저장소를 통해 데이터베이스에 액세스하는 모든 클래스에 대해 레코드 수준의 비즈니스 제약 조건을 달성 할 수 있습니다. (응용 프로그램의 상위 레벨에서 사용자 정의 DQL을 조심하십시오).

예 : 인증에

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

저장소에 액세스 제어 코드를 삽입하는 것에 동의합니다. 저장소는 응용 프로그램의 비즈니스 논리에 대해 아무 것도 모를 수 있습니다. 데이터 검색에만 관심을 가져야합니다. 비즈니스 로직은 상위 수준의 서비스와 클래스를위한 것입니다. – moteutsch

+0

Doctrine 구현에서 저장소를 추상화하는 것도 좋은 생각이라고 생각합니다. 상속 대신에 (기본 Doctrine 저장소 클래스의) composition을 사용하십시오. 이것은 인터페이스 코딩과 결합되어 일부 또는 모든 리포지토리 (Dopprine 저장소와의 혼동을 피하기 위해 "매퍼 (mappers)"라고 부르는 것)에서 데이터 소스를 쉽게 바꿀 수 있습니다. – moteutsch

관련 문제