Programing

MVC에서 모델을 어떻게 구성해야합니까?

lottogame 2020. 2. 10. 22:02
반응형

MVC에서 모델을 어떻게 구성해야합니까? [닫은]


MVC 프레임 워크를 파악하고 모델에 얼마나 많은 코드가 들어가야하는지 종종 궁금합니다. 나는 다음과 같은 메소드를 가진 데이터 액세스 클래스를 사용하는 경향이 있습니다.

public function CheckUsername($connection, $username)
{
    try
    {
        $data = array();
        $data['Username'] = $username;

        //// SQL
        $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";

        //// Execute statement
        return $this->ExecuteObject($connection, $sql, $data);
    }
    catch(Exception $e)
    {
        throw $e;
    }
}

내 모델은 데이터베이스 테이블에 매핑되는 엔터티 클래스 인 경향이 있습니다.

모델 객체에 위의 코드뿐만 아니라 모든 데이터베이스 매핑 속성이 있어야합니까, 아니면 실제로 데이터베이스가 작동하는 코드를 분리해도 괜찮습니까?

결국 네 개의 레이어가 있습니까?


면책 조항 : 다음은 PHP 기반 웹 응용 프로그램의 맥락에서 MVC와 같은 패턴을 이해하는 방법에 대한 설명입니다. 내용에 사용 된 모든 외부 링크는 용어와 개념을 설명하기 위해 있으며 주제에 대한 내 자신의 신뢰성을 암시 하지 않습니다 .

가장 먼저 정리해야 할 것은 모델은 레이어 입니다.

둘째 : 클래식 MVC 와 웹 개발에 사용하는 것에는 차이 가 있습니다. 여기에 내가 쓴 오래된 답변이 있는데, 어떻게 다른지 간단히 설명합니다.

모델이 아닌 것 :

모델은 클래스 또는 단일 객체가 아닙니다. 대부분의 프레임 워크가 이러한 오해를 영속시키기 때문에 (나도 그렇게 배우기 시작했을 때 원래 답변이 쓰여졌지만) 그렇게 하는 것은 매우 일반적인 실수 입니다.

ORM (Object-Relational Mapping) 기술이나 데이터베이스 테이블의 추상화도 아닙니다. 달리 말하면 누구나 다른 새로운 ORM 또는 전체 프레임 워크 '판매' 하려고 할 것 입니다.

모델이란?

적절한 MVC 적응에서 M은 모든 도메인 비즈니스 로직을 포함하고 모델 계층주로 세 가지 유형의 구조로 구성됩니다.

  • 도메인 객체

    도메인 객체는 순수한 도메인 정보의 논리적 컨테이너입니다. 일반적으로 문제점 도메인 공간의 논리 엔티티를 나타냅니다. 일반적으로 비즈니스 로직 이라고합니다 .

    여기에서 송장을 보내기 전에 데이터의 유효성을 검사하거나 주문의 총 비용을 계산하는 방법을 정의 할 수 있습니다. 동시에, 도메인 객체가 저장 전혀 모르고있다 -도에서 경우 (SQL 데이터베이스, REST API를, 텍스트 파일 등)도 심지어는 경우 가 저장하거나 검색하세요.

  • 데이터 매퍼

    이러한 객체는 스토리지에만 책임이 있습니다. 데이터베이스에 정보를 저장하면 SQL이있는 곳이됩니다. 또는 XML 파일을 사용하여 데이터를 저장하고 데이터 매퍼 가 XML 파일에서 구문 분석하고 있습니다.

  • 서비스

    이를 "상위 도메인 개체"라고 생각할 수 있지만 비즈니스 논리 대신 서비스도메인 개체매퍼 간의 상호 작용을 담당합니다 . 이러한 구조는 결국 도메인 비즈니스 로직과 상호 작용하기위한 "공용"인터페이스를 만듭니다. 이를 피할 수는 있지만 일부 도메인 로직을 컨트롤러 로 유출하는 경우가 있습니다 .

    ACL 구현 질문 에이 주제에 대한 관련 답변이 있습니다. 유용 할 수 있습니다.

모델 계층과 MVC 트라이어드의 다른 부분 간의 통신은 서비스를 통해서만 이루어져야 합니다 . 명확한 분리는 몇 가지 추가 이점이 있습니다.

  • 단일 책임 원칙 (SRP) 을 시행하는 데 도움이됩니다.
  • 논리가 변경 될 경우 추가 '흔들기 방'제공
  • 컨트롤러를 최대한 간단하게 유지
  • 외부 API가 필요한 경우 명확한 청사진을 제공합니다.

 

모델과 상호 작용하는 방법?

전제 조건 : "글로벌 스테이트 및 싱글 톤""사물을 찾지 마십시오!" 강의 시청 클린 코드 토크에서.

서비스 인스턴스에 대한 액세스 권한 얻기

이러한 서비스에 액세스하기 위해 ViewController 인스턴스 ( "UI 계층"이라고 할 수있는) 모두에 대해 두 가지 일반적인 접근 방식이 있습니다.

  1. DI 컨테이너를 사용하여 뷰 및 컨트롤러의 생성자에 필요한 서비스를 직접 주입 할 수 있습니다.
  2. 모든 뷰 및 컨트롤러에 대한 필수 종속성으로 서비스 팩토리를 사용합니다.

의심 할 수 있듯이 DI 컨테이너는 훨씬 더 우아한 솔루션입니다 (초보자에게는 가장 쉬운 방법은 아님). 이 기능을 고려할 것을 권장하는 두 라이브러리는 Syfmony의 독립형 DependencyInjection 구성 요소 또는 Auryn 입니다.

팩토리와 DI 컨테이너를 사용하는 솔루션을 사용하면 선택한 컨트롤러간에 다양한 서버 인스턴스를 공유하고 지정된 요청-응답주기 동안 볼 수 있습니다.

모델 상태의 변경

이제 컨트롤러에서 모델 레이어에 액세스 할 수 있으므로 실제로 사용하기 시작해야합니다.

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $identity = $this->identification->findIdentityByEmailAddress($email);
    $this->identification->loginWithPassword(
        $identity,
        $request->get('password')
    );
}

컨트롤러는 매우 명확한 작업을 수행합니다. 사용자 입력을 가져 와서이 입력을 기반으로 비즈니스 로직의 현재 상태를 변경하십시오. 이 예에서 사이에 변경된 상태는 "익명 사용자"및 "로그인 한 사용자"입니다.

Controller는 사용자 입력의 유효성을 검사 할 책임이 없습니다. 비즈니스 규칙의 일부이고 Controller가 여기 또는 여기에서 볼 수있는 것과 같은 SQL 쿼리를 호출 하지 않기 때문입니다 (그들을 미워하지 말고 악의적 인 것이 아니라 잘못 안내하지 마십시오).

사용자에게 상태 변경을 표시합니다.

사용자가 로그인했거나 실패했습니다. 이제 뭐? 상기 사용자는 여전히 그것을 알지 못한다. 따라서 실제로 응답을 생성해야하며 이는 뷰의 책임입니다.

public function postLogin()
{
    $path = '/login';
    if ($this->identification->isUserLoggedIn()) {
        $path = '/dashboard';
    }
    return new RedirectResponse($path); 
}

이 경우 뷰는 현재 모델 계층 상태에 따라 두 가지 가능한 응답 중 하나를 생성했습니다. 다른 유스 케이스의 경우 "현재 선택된 기사"와 같은 것을 기반으로 렌더링 할 다른 템플리트를 선택하는보기가 있습니다.

프리젠 테이션 레이어는 실제로 PHP에서 MVC보기 이해에 설명 된대로 상당히 정교해질 수 있습니다 .

그러나 나는 단지 REST API를 만들고 있습니다!

물론 이것이 과잉 인 상황이 있습니다.

MVC는 우려 분리 원칙 에 대한 구체적인 솔루션 일뿐 입니다. MVC는 비즈니스 로직과 사용자 인터페이스를 분리하고 UI에서는 사용자 입력과 프리젠 테이션 처리를 분리했습니다. 이것은 중요합니다. 사람들은 종종 그것을 "세가지"로 묘사하지만 실제로 세 개의 독립적 인 부분으로 구성되지는 않습니다. 구조는 다음과 같습니다.

MVC 분리

즉, 프리젠 테이션 레이어의 논리가 존재하지 않는 것에 가깝다면 실용적인 접근 방식은 단일 레이어로 유지하는 것입니다. 또한 모델 레이어의 일부 측면을 실질적으로 단순화 할 수 있습니다.

이 방법을 사용하면 로그인 예제 (API 용)를 다음과 같이 작성할 수 있습니다.

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $data = [
        'status' => 'ok',
    ];
    try {
        $identity = $this->identification->findIdentityByEmailAddress($email);
        $token = $this->identification->loginWithPassword(
            $identity,
            $request->get('password')
        );
    } catch (FailedIdentification $exception) {
        $data = [
            'status' => 'error',
            'message' => 'Login failed!',
        ]
    }

    return new JsonResponse($data);
}

이것이 지속 가능하지는 않지만 응답 본문을 렌더링하기위한 논리가 복잡 할 때이 단순화는보다 사소한 시나리오에 매우 유용합니다. 그러나 경고 복잡한 프리젠 테이션 로직 큰 코드베이스에서 사용하려고 할 때이 방법은, 악몽이 될 것이다.

 

모델을 구축하는 방법?

위에서 설명한 것처럼 단일 "Model"클래스가 없기 때문에 실제로 "모델을 빌드하지"마십시오. 대신 특정 방법을 수행 할 수있는 서비스 만들기부터 시작하십시오 . 그런 다음 Domain Objects and Mappers 를 구현 합니다.

서비스 방법의 예 :

위의 두 가지 방법 모두에서 식별 서비스에 대한이 로그인 방법이있었습니다. 실제로 어떤 모습일까요? 내가 라이브러리 에서 동일한 기능의 약간 수정 된 버전을 사용하고 있습니다 .

public function loginWithPassword(Identity $identity, string $password): string
{
    if ($identity->matchPassword($password) === false) {
        $this->logWrongPasswordNotice($identity, [
            'email' => $identity->getEmailAddress(),
            'key' => $password, // this is the wrong password
        ]);

        throw new PasswordMismatch;
    }

    $identity->setPassword($password);
    $this->updateIdentityOnUse($identity);
    $cookie = $this->createCookieIdentity($identity);

    $this->logger->info('login successful', [
        'input' => [
            'email' => $identity->getEmailAddress(),
        ],
        'user' => [
            'account' => $identity->getAccountId(),
            'identity' => $identity->getId(),
        ],
    ]);

    return $cookie->getToken();
}

보시다시피,이 추상화 수준에서는 데이터가 어디서 가져 왔는지에 대한 표시가 없습니다. 데이터베이스 일 수도 있지만 테스트 목적으로 모의 객체 일 수도 있습니다. 실제로 사용되는 데이터 맵퍼조차도이 private서비스 방법에 숨겨져 있습니다 .

private function changeIdentityStatus(Entity\Identity $identity, int $status)
{
    $identity->setStatus($status);
    $identity->setLastUsed(time());
    $mapper = $this->mapperFactory->create(Mapper\Identity::class);
    $mapper->store($identity);
}

매퍼를 만드는 방법

지속성의 추상화를 구현하기 위해 가장 유연한 접근 방식은 사용자 정의 데이터 맵퍼 를 작성하는 것 입니다.

매퍼 다이어그램

부터 : PoEAA book

실제로는 특정 클래스 또는 수퍼 클래스와의 상호 작용을 위해 구현됩니다. 당신이 말할 수 있습니다 CustomerAdmin코드에서 (모두에서 상속 User슈퍼 클래스). 둘 다 서로 다른 필드를 포함하기 때문에 아마도 별도의 일치하는 매퍼를 갖게 될 것입니다. 그러나 공유되고 일반적으로 사용되는 작업으로 끝납니다. 예를 들어 "마지막 온라인" 시간 업데이트 . 기존 매퍼를보다 복잡하게 만드는 대신보다 실용적인 접근 방식은 일반적인 "사용자 매퍼"를 사용하여 해당 타임 스탬프 만 업데이트하는 것입니다.

추가 의견 :

  1. 데이터베이스 테이블 및 모델

    데이터베이스 테이블, Domain ObjectMapper 간에 직접적인 1 : 1 : 1 관계가있는 경우가 많지만 대규모 프로젝트에서는 예상보다 덜 일반적 일 수 있습니다.

    • 단일 도메인 오브젝트사용하는 정보 는 다른 테이블에서 맵핑 될 수 있지만 오브젝트 자체는 데이터베이스에 지속성이 없습니다.

      예 : 월간 보고서를 생성하는 경우 이것은 다른 테이블에서 정보를 수집하지만 MonthlyReport데이터베이스 에는 마법의 테이블 이 없습니다 .

    • 단일 매퍼 는 여러 테이블에 영향을 줄 수 있습니다.

      예 :User 객체의 데이터를 저장하는 경우이 도메인 객체 에는 다른 도메인 객체 ( Group인스턴스) 모음이 포함될 수 있습니다 . 당신이 그들을 변경하고 저장할 경우 User데이터 매퍼 갱신 및 / 또는 여러 테이블에서 항목을 삽입해야합니다.

    • 단일 도메인 개체의 데이터 는 둘 이상의 테이블에 저장됩니다.

      예 : 대규모 시스템 (생각 : 중간 규모의 소셜 네트워크)에서는 사용자 인증 데이터와 자주 액세스하는 데이터를 더 큰 콘텐츠 청크와 별도로 저장하는 것이 실용적 일 수 있습니다. 이 경우 여전히 단일 User클래스 가있을 수 있지만 포함 된 정보는 전체 세부 사항을 가져 왔는지 여부에 따라 다릅니다.

    • 모든 도메인 개체에 대해 둘 이상의 매퍼가있을 수 있습니다

      예 : 공용 소프트웨어와 관리 소프트웨어를위한 공유 코드 기반 뉴스 사이트가 있습니다. 그러나 두 인터페이스 모두 동일한 Article클래스를 사용하지만 관리에는 더 많은 정보가 필요합니다. 이 경우 "내부"와 "외부"의 두 가지 별도 맵퍼가 있습니다. 각각 다른 쿼리를 수행하거나 심지어 다른 데이터베이스를 사용합니다 (마스터 또는 슬레이브에서와 같이).

  2. 보기는 템플릿이 아닙니다

    MVC의 인스턴스 보기 (패턴의 MVP 변형을 사용하지 않는 경우)는 프리젠 테이션 로직을 담당합니다. 즉, 각 보기 는 일반적으로 최소한 몇 개의 템플릿을 저글링합니다. 모델 레이어 에서 데이터를 획득 한 다음 수신 된 정보를 기반으로 템플릿을 선택하고 값을 설정합니다.

    이로부터 얻을 수있는 이점 중 하나는 재사용 성입니다. ListView잘 작성된 코드 를 사용하여 클래스 를 만들면 기사 아래에 사용자 목록 및 주석을 표시하는 동일한 클래스를 가질 수 있습니다. 둘 다 동일한 프리젠 테이션 로직을 가지고 있기 때문입니다. 템플릿 만 전환하면됩니다.

    네이티브 PHP 템플릿을 사용하거나 타사 템플릿 엔진을 사용할 수 있습니다 . View 인스턴스 를 완전히 대체 할 수있는 타사 라이브러리도있을 수 있습니다 .

  3. 이전 버전의 답변은 어떻습니까?

    유일한 주요 변경 사항은 이전 버전에서 Model 이라는 모델 이 실제로 서비스라는 것 입니다. "라이브러리 비유"의 나머지 부분은 꽤 잘 유지됩니다.

    내가 볼 수있는 유일한 결함은 이것이 도서관에서 당신에게 정보를 반환 할 것이기 때문에 실제로 이상한 도서관 일 것입니다. 그렇지 않으면 추상화 자체가 "누설"시작하기 때문에 책 자체를 만지지 마십시오. 더 적합한 유추를 생각해야 할 수도 있습니다.

  4. ViewController 인스턴스 의 관계는 무엇입니까 ?

    MVC 구조는 ui와 model의 두 계층으로 구성됩니다. UI 계층 의 주요 구조 는 뷰와 컨트롤러입니다.

    MVC 디자인 패턴을 사용하는 웹 사이트를 처리 할 때 가장 좋은 방법은 뷰와 컨트롤러간에 1 : 1 관계를 유지하는 것입니다. 각보기는 웹 사이트의 전체 페이지를 나타내며 해당 특정보기에 대한 모든 수신 요청을 처리하는 전용 컨트롤러가 있습니다.

    예를 들어, 열린 기사를 나타내려면 \Application\Controller\Document이 있어야 \Application\View\Document합니다. 여기에는 기사를 처리 할 때 UI 계층의 모든 주요 기능이 포함됩니다 (물론 기사와 직접 관련이없는 일부 XHR 구성 요소가 있을 수 있음 ) .


비즈니스 로직 인 모든 것은 데이터베이스 쿼리, 계산, REST 호출 등 모델에 속합니다.

모델 자체에서 데이터 액세스 권한을 가질 수 있지만 MVC 패턴은이를 수행하는 것을 제한하지 않습니다. 서비스, ​​매퍼 등으로 설탕 코팅을 할 수 있지만 모델의 실제 정의는 비즈니스 로직을 처리하는 계층입니다. 클래스, 함수 또는 원하는 경우 gazillion 객체가있는 완전한 모듈 일 수 있습니다.

모델에서 직접 데이터베이스 쿼리를 실행하지 않고 실제로 데이터베이스 쿼리를 실행하는 별도의 개체를 갖는 것이 항상 더 쉽습니다. 이는 모델을 테스트 할 때 (모델에 모의 데이터베이스 종속성을 쉽게 주입 할 수 있기 때문에) 특히 유용합니다.

class Database {
   protected $_conn;

   public function __construct($connection) {
       $this->_conn = $connection;
   }

   public function ExecuteObject($sql, $data) {
       // stuff
   }
}

abstract class Model {
   protected $_db;

   public function __construct(Database $db) {
       $this->_db = $db;
   }
}

class User extends Model {
   public function CheckUsername($username) {
       // ...
       $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE ...";
       return $this->_db->ExecuteObject($sql, $data);
   }
}

$db = new Database($conn);
$model = new User($db);
$model->CheckUsername('foo');

또한 PHP에서는 백 트레이스가 유지되므로 특히 예제와 같은 경우 예외를 잡거나 다시 던질 필요가 거의 없습니다. 예외를 발생시키고 대신 컨트롤러에서 예외를 잡아라.


웹 "MVC"에서는 원하는대로 할 수 있습니다.

원래 개념 (1) 은 모델을 비즈니스 로직으로 설명했습니다. 응용 프로그램 상태를 나타내고 일부 데이터 일관성을 강화해야합니다. 이러한 접근 방식은 종종 "뚱뚱한 모델"로 설명됩니다.

대부분의 PHP 프레임 워크는 모델이 단지 데이터베이스 인터페이스 인보다 얕은 접근 방식을 따릅니다. 그러나 최소한 이러한 모델은 여전히 ​​들어오는 데이터와 관계를 확인해야합니다.

어느 쪽이든 SQL 항목 또는 데이터베이스 호출을 다른 계층으로 분리하면 크게 멀지 않습니다. 이런 식으로 실제 스토리지 API가 아닌 실제 데이터 / 동작에만 관심을 가져야합니다. (하지만 과도하게 수행하는 것은 부당합니다. 예를 들어, 데이터베이스 백엔드가 사전에 설계되지 않은 경우 파일 스토리지로 교체 할 수는 없습니다.)


더 oftenly 대부분의 응용 프로그램 데이터, 디스플레이, 처리 부분이있을 것이다 그리고 우리는 단지 문자의 모든 사람들을 넣어 M, V하고 C.

모델 ( M) -> 응용 프로그램의 상태를 유지하는 특성을 가지고 있으며, 그것에 대해 아무 것도 알지 해달라고 V하고 C.

View ( V) -> 응용 프로그램의 형식을 표시하며 다이제스트하는 방법 만 알고 있으며 신경 쓰지 않습니다 C.

컨트롤러 ( C) ---> 어플리케이션 처리부를 가지며, M 및 V 사이의 배선으로 작용하고 양에 따라 M, V달리 M하고 V.

서로간에 관심의 분리가 있습니다. 향후에는 변경이나 개선 사항을 매우 쉽게 추가 할 수 있습니다.


제 경우에는 쿼리, 페치 등과 같은 모든 직접적인 데이터베이스 상호 작용을 처리하는 데이터베이스 클래스가 있습니다. 따라서 데이터베이스를 MySQL 에서 PostgreSQL 로 변경해야한다면 아무런 문제가 없습니다. 추가 레이어를 추가하면 유용 할 수 있습니다.

각 테이블에는 고유 한 클래스와 고유 한 메소드가있을 수 있지만 실제로 데이터를 가져 오려면 데이터베이스 클래스가이를 처리 할 수 ​​있습니다.

파일 Database.php

class Database {
    private static $connection;
    private static $current_query;
    ...

    public static function query($sql) {
        if (!self::$connection){
            self::open_connection();
        }
        self::$current_query = $sql;
        $result = mysql_query($sql,self::$connection);

        if (!$result){
            self::close_connection();
            // throw custom error
            // The query failed for some reason. here is query :: self::$current_query
            $error = new Error(2,"There is an Error in the query.\n<b>Query:</b>\n{$sql}\n");
            $error->handleError();
        }
        return $result;
    }
 ....

    public static function find_by_sql($sql){
        if (!is_string($sql))
            return false;

        $result_set = self::query($sql);
        $obj_arr = array();
        while ($row = self::fetch_array($result_set))
        {
            $obj_arr[] = self::instantiate($row);
        }
        return $obj_arr;
    }
}

테이블 객체 classL

class DomainPeer extends Database {

    public static function getDomainInfoList() {
        $sql = 'SELECT ';
        $sql .='d.`id`,';
        $sql .='d.`name`,';
        $sql .='d.`shortName`,';
        $sql .='d.`created_at`,';
        $sql .='d.`updated_at`,';
        $sql .='count(q.id) as queries ';
        $sql .='FROM `domains` d ';
        $sql .='LEFT JOIN queries q on q.domainId = d.id ';
        $sql .='GROUP BY d.id';
        return self::find_by_sql($sql);
    }

    ....
}

이 예제가 좋은 구조를 만드는 데 도움이되기를 바랍니다.

참고 URL : https://stackoverflow.com/questions/5863870/how-should-a-model-be-structured-in-mvc



반응형