PHP에서 적절한 리포지토리 패턴 디자인?
서문 : 관계형 데이터베이스와 함께 MVC 아키텍처에서 리포지토리 패턴을 사용하려고합니다.
최근 PHP에서 TDD를 배우기 시작했으며 데이터베이스가 나머지 응용 프로그램과 너무 밀접하게 연결되어 있음을 깨닫고 있습니다. 리포지토리에 대해 읽고 IoC 컨테이너 를 사용하여 컨트롤러에 "주입"했습니다. 아주 멋진 것들. 그러나 이제 저장소 디자인에 대한 실질적인 질문이 있습니다. 다음 예를 고려하십시오.
<?php
class DbUserRepository implements UserRepositoryInterface
{
protected $db;
public function __construct($db)
{
$this->db = $db;
}
public function findAll()
{
}
public function findById($id)
{
}
public function findByName($name)
{
}
public function create($user)
{
}
public function remove($user)
{
}
public function update($user)
{
}
}
이슈 # 1 : 너무 많은 필드
이러한 모든 find 메소드는 모든 필드 선택 ( SELECT *
) 방법을 사용합니다. 그러나 내 응용 프로그램에서는 항상 오버 헤드를 추가하고 작업 속도를 늦추기 때문에 내가 얻는 필드 수를 제한하려고합니다. 이 패턴을 사용하는 사람들은 이것을 어떻게 처리합니까?
문제 # 2 : 너무 많은 방법
이 수업은 지금은 멋져 보이지만 실제 응용 프로그램에는 더 많은 방법이 필요하다는 것을 알고 있습니다. 예를 들면 다음과 같습니다.
- findAllByNameAndStatus
- findAllInCountry
- findAllWithEmailAddressSet
- 모두 찾기 나이 및 성별
- 모두 모두와 성별 주문
- 기타.
보시다시피, 가능한 방법 목록이 매우 길 수 있습니다. 그런 다음 위의 필드 선택 문제를 추가하면 문제가 악화됩니다. 과거에는 일반적으로 컨트롤러 에이 논리를 모두 넣었습니다.
<?php
class MyController
{
public function users()
{
$users = User::select('name, email, status')
->byCountry('Canada')->orderBy('name')->rows();
return View::make('users', array('users' => $users));
}
}
내 저장소 접근 방식으로 다음과 같이 끝내고 싶지 않습니다.
<?php
class MyController
{
public function users()
{
$users = $this->repo->get_first_name_last_name_email_username_status_by_country_order_by_name('Canada');
return View::make('users', array('users' => $users))
}
}
문제 # 3 : 인터페이스와 일치하지 않습니다
리포지토리에 인터페이스를 사용하면 이점이 있으므로 테스트 목적으로 또는 구현을 위해 구현을 바꿀 수 있습니다. 인터페이스에 대한 이해는 구현이 따라야하는 계약을 정의한다는 것입니다. 이것은 저장소에 추가 메소드를 추가하기 시작할 때까지 좋습니다 findAllInCountry()
. 이제 인터페이스를 업데이트 하여이 메소드를 가져와야합니다. 그렇지 않으면 다른 구현에는 없을 수 있으며 응용 프로그램이 손상 될 수 있습니다. 이것에 의해 미친 느낌이 듭니다 ... 꼬리 개가 흔들리는 경우.
사양 패턴?
이 리드 나 저장소는 (같은 방법의 고정 번호를 가지고해야한다고 생각합니다 save()
, remove()
, find()
, findAll()
, 등). 그러나 특정 조회를 어떻게 실행합니까? 사양 패턴에 대해 들어 보았지만 IsSatisfiedBy()
데이터베이스를 가져 오는 경우 전체 레코드 세트 만 줄어 듭니다 .
도움?
분명히, 리포지토리를 사용할 때 조금 생각해야합니다. 누구든지 이것이 어떻게 가장 잘 처리되는지 깨달을 수 있습니까?
나는 내 자신의 질문에 답하는 데 열중 할 것이라고 생각했다. 다음은 원래 질문에서 1-3 문제를 해결하는 한 가지 방법입니다.
면책 조항 : 패턴이나 기술을 설명 할 때 항상 올바른 용어를 사용하지는 않습니다. 그 죄송합니다.
목표 :
- 보고 편집 할 기본 컨트롤러의 완전한 예를 작성하십시오
Users
. - 모든 코드는 완벽하게 테스트하고 조롱 할 수 있어야합니다.
- 컨트롤러는 데이터가 저장된 위치를 알 수 없어야합니다 (즉, 변경할 수 있음).
- SQL 구현을 보여주는 예제 (가장 일반적)
- 성능을 극대화하려면 컨트롤러는 추가 필드없이 필요한 데이터 만 수신해야합니다.
- 구현시 쉽게 개발할 수 있도록 일부 유형의 데이터 매퍼를 사용해야합니다.
- 구현시 복잡한 데이터 조회를 수행 할 수 있어야합니다.
해결책
영구 저장소 (데이터베이스) 상호 작용을 R (읽기) 및 CUD (만들기, 업데이트, 삭제)의 두 가지 범주로 나누고 있습니다. 내 경험에 따르면 읽기는 실제로 응용 프로그램 속도를 저하시키는 원인입니다. 그리고 데이터 조작 (CUD)은 실제로 속도는 느리지 만 훨씬 덜 자주 발생하므로 걱정할 필요가 없습니다.
CUD (만들기, 업데이트, 삭제)는 쉽습니다. 여기에는 실제 모델 작업이 포함 Repositories
되며 지속성을 위해 전달됩니다 . 내 리포지토리는 여전히 Read 메서드를 제공하지만 단순히 개체를 만들지 만 표시하지는 않습니다. 나중에 더 자세히.
R (읽기)은 쉽지 않습니다. 여기에는 모델이 없으며 단지 objects 만 사용하십시오 . 원하는 경우 배열을 사용하십시오 . 이러한 객체는 단일 모델이거나 많은 모델이 혼합 된 형태 일 수 있습니다. 이것 자체는 그다지 흥미롭지 않지만 어떻게 생성 되는가입니다. 내가 부르는 것을 사용하고 Query Objects
있습니다.
코드:
사용자 모델
기본 사용자 모델로 간단하게 시작하겠습니다. ORM 확장 또는 데이터베이스 항목이 전혀 없습니다. 순수한 모델의 영광. 게터, 세터, 유효성 검사 등을 추가하십시오.
class User
{
public $id;
public $first_name;
public $last_name;
public $gender;
public $email;
public $password;
}
리포지토리 인터페이스
사용자 저장소를 작성하기 전에 저장소 인터페이스를 작성하려고합니다. 이것은 내 컨트롤러가 사용하기 위해 리포지토리가 따라야하는 "계약"을 정의합니다. 내 컨트롤러는 데이터가 실제로 저장된 위치를 알지 못합니다.
내 저장소에는이 세 가지 방법 만 포함됩니다. 이 save()
메소드는 사용자 오브젝트에 ID 세트가 있는지 여부에 따라 사용자 작성 및 업데이트를 담당합니다.
interface UserRepositoryInterface
{
public function find($id);
public function save(User $user);
public function remove(User $user);
}
SQL 리포지토리 구현
이제 인터페이스 구현을 만듭니다. 언급했듯이 내 예제는 SQL 데이터베이스를 사용하는 것입니다. 반복적 인 SQL 쿼리를 작성하지 않으려면 데이터 매퍼 를 사용하십시오 .
class SQLUserRepository implements UserRepositoryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function find($id)
{
// Find a record with the id = $id
// from the 'users' table
// and return it as a User object
return $this->db->find($id, 'users', 'User');
}
public function save(User $user)
{
// Insert or update the $user
// in the 'users' table
$this->db->save($user, 'users');
}
public function remove(User $user)
{
// Remove the $user
// from the 'users' table
$this->db->remove($user, 'users');
}
}
쿼리 개체 인터페이스
이제 저장소에서 CUD (Create, Update, Delete)를 처리하여 R (읽기) 에 집중할 수 있습니다 . 쿼리 개체는 단순히 일부 유형의 데이터 조회 논리를 캡슐화 한 것입니다. 이들은 쿼리 빌더 가 아닙니다 . 저장소와 같이 추상화함으로써 구현을 변경하고 쉽게 테스트 할 수 있습니다. 쿼리 개체의 예는 AllUsersQuery
또는 또는 또는 AllActiveUsersQuery
일 수 있습니다 MostCommonUserFirstNames
.
"내 쿼리에서 해당 쿼리에 대한 메소드를 작성할 수는 없습니까?" 예, 그러나 내가 이것을하지 않는 이유는 다음과 같습니다.
- 내 저장소는 모델 객체로 작업하기위한 것입니다. 실제 앱에서
password
모든 사용자를 나열 하려면 왜 필드를 가져와야합니까? - 리포지토리는 종종 모델마다 다르지만 쿼리에는 종종 두 개 이상의 모델이 포함됩니다. 그렇다면 어떤 저장소에 메소드를 넣었습니까?
- 이렇게하면 부풀린 클래스가 아닌 내 리포지토리를 매우 간단하게 유지할 수 있습니다.
- 모든 쿼리는 이제 자체 클래스로 구성됩니다.
- 실제로 현재 데이터베이스 저장소를 추상화하기 위해 저장소가 있습니다.
이 예제에서는 "AllUsers"를 조회하는 쿼리 객체를 작성합니다. 인터페이스는 다음과 같습니다.
interface AllUsersQueryInterface
{
public function fetch($fields);
}
쿼리 객체 구현
여기에서 데이터 매퍼를 다시 사용하여 개발 속도를 높일 수 있습니다. 반환 된 데이터 세트 인 필드를 한 번 조정할 수 있습니다. 이것은 수행 된 쿼리 조작과 관련이 있습니다. 내 쿼리 개체는 쿼리 작성기가 아닙니다. 그들은 단순히 특정 쿼리를 수행합니다. 그러나 여러 가지 상황에서 아마도이 것을 많이 사용한다는 것을 알고 있기 때문에 필자는 필드를 지정할 수있는 능력을 부여하고 있습니다. 필요없는 필드를 반환하고 싶지 않습니다!
class AllUsersQuery implements AllUsersQueryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function fetch($fields)
{
return $this->db->select($fields)->from('users')->orderBy('last_name, first_name')->rows();
}
}
컨트롤러로 넘어 가기 전에 이것이 얼마나 강력한지를 보여주는 또 다른 예를 보여 드리고자합니다. 보고 엔진이 있고에 대한 보고서를 만들어야 할 수도 있습니다 AllOverdueAccounts
. 내 데이터 매퍼로 까다로울 수 있으며이 SQL
상황에서 실제로 쓸 수 있습니다. 문제 없습니다.이 쿼리 객체는 다음과 같습니다.
class AllOverdueAccountsQuery implements AllOverdueAccountsQueryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function fetch()
{
return $this->db->query($this->sql())->rows();
}
public function sql()
{
return "SELECT...";
}
}
이렇게하면이 보고서에 대한 모든 논리가 한 클래스로 유지되며 테스트하기 쉽습니다. 나는 그것을 내 마음 내용에 조롱하거나 심지어 다른 구현을 완전히 사용할 수 있습니다.
컨트롤러
이제 재미있는 부분은 모든 조각을한데 모으는 것입니다. 의존성 주입을 사용하고 있습니다. 일반적으로 종속성은 생성자에 주입되지만 실제로는 컨트롤러 메소드 (라우트)에 직접 주입하는 것을 선호합니다. 이것은 컨트롤러의 객체 그래프를 최소화하며 실제로 더 읽기 쉽습니다. 이 접근법이 마음에 들지 않으면 전통적인 생성자 메소드를 사용하십시오.
class UsersController
{
public function index(AllUsersQueryInterface $query)
{
// Fetch user data
$users = $query->fetch(['first_name', 'last_name', 'email']);
// Return view
return Response::view('all_users.php', ['users' => $users]);
}
public function add()
{
return Response::view('add_user.php');
}
public function insert(UserRepositoryInterface $repository)
{
// Create new user model
$user = new User;
$user->first_name = $_POST['first_name'];
$user->last_name = $_POST['last_name'];
$user->gender = $_POST['gender'];
$user->email = $_POST['email'];
// Save the new user
$repository->save($user);
// Return the id
return Response::json(['id' => $user->id]);
}
public function view(SpecificUserQueryInterface $query, $id)
{
// Load user data
if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
return Response::notFound();
}
// Return view
return Response::view('view_user.php', ['user' => $user]);
}
public function edit(SpecificUserQueryInterface $query, $id)
{
// Load user data
if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
return Response::notFound();
}
// Return view
return Response::view('edit_user.php', ['user' => $user]);
}
public function update(UserRepositoryInterface $repository)
{
// Load user model
if (!$user = $repository->find($id)) {
return Response::notFound();
}
// Update the user
$user->first_name = $_POST['first_name'];
$user->last_name = $_POST['last_name'];
$user->gender = $_POST['gender'];
$user->email = $_POST['email'];
// Save the user
$repository->save($user);
// Return success
return true;
}
public function delete(UserRepositoryInterface $repository)
{
// Load user model
if (!$user = $repository->find($id)) {
return Response::notFound();
}
// Delete the user
$repository->delete($user);
// Return success
return true;
}
}
마지막 생각들:
여기서 주목해야 할 중요한 사항은 엔터티를 수정 (작성, 업데이트 또는 삭제) 할 때 실제 모델 객체로 작업하고 리포지토리를 통해 지속성을 수행한다는 것입니다.
그러나 표시 할 때 (데이터를 선택하고보기로 전송) 모델 객체가 아니라 평범한 오래된 값 객체로 작업하고 있습니다. 필요한 필드 만 선택하고 데이터 조회 성능을 극대화 할 수 있도록 설계되었습니다.
내 리포지토리는 매우 깨끗하게 유지되며 대신이 "메시"가 내 모델 쿼리로 구성됩니다.
일반적인 작업을 위해 반복적 인 SQL을 작성하는 것은 우스운 일이므로 데이터 매퍼를 사용하여 개발에 도움을줍니다. 그러나 필요한 경우 SQL을 작성할 수 있습니다 (복잡한 쿼리,보고 등). 그리고 당신이 할 때, 그것은 제대로 명명 된 클래스에 잘 들어갑니다.
내 접근 방식에 대한 귀하의 의견을 듣고 싶습니다!
2015 년 7 월 업데이트 :
나는이 모든 것으로 끝나는 의견에 질문을 받았다. 글쎄, 실제로 그렇게 멀지는 않습니다. 사실, 나는 여전히 저장소를 좋아하지 않습니다. 기본 조회 (특히 이미 ORM을 사용하는 경우)에 대해 과도하게 사용되며 더 복잡한 쿼리로 작업 할 때 지저분합니다.
일반적으로 ActiveRecord 스타일 ORM을 사용하므로 대부분의 경우 응용 프로그램 전체에서 직접 해당 모델을 참조합니다. 그러나 더 복잡한 쿼리가있는 상황에서는 쿼리 개체를 사용하여 더 재사용 할 수 있도록합니다. 또한 항상 내 모델을 내 메소드에 주입하여 테스트에서 쉽게 조롱 할 수 있습니다.
내 경험을 바탕으로 귀하의 질문에 대한 답변은 다음과 같습니다.
Q : 필요하지 않은 필드를 다시 가져 오려면 어떻게해야합니까?
A : 경험상 이것은 완전한 엔티티 대 임시 쿼리를 다루는 것으로 요약됩니다.
완전한 실체는 User
객체 와 같습니다 . 속성과 메서드 등이 있습니다. 코드베이스에서 일류 시민입니다.
임시 쿼리는 일부 데이터를 반환하지만 그 이상은 알 수 없습니다. 데이터가 애플리케이션 주위로 전달되면 컨텍스트없이 수행됩니다. 그것은 User
입니까? User
일부 Order
정보는 첨부? 우리는 정말로 모른다.
나는 완전한 실체를 다루는 것을 선호합니다
자주 사용하지 않는 데이터를 다시 가져 오는 것이 맞지만 다양한 방법으로이 문제를 해결할 수 있습니다.
- 엔터티를 적극적으로 캐시하므로 데이터베이스에서 한 번만 읽기 가격을 지불하십시오.
- 엔터티를 모델링하는 데 더 많은 시간을 할애하여 엔터티를 잘 구분하십시오. (큰 엔터티를 두 개의 작은 엔터티 등으로 나누는 것을 고려하십시오.)
- 여러 버전의 엔티티를 고려하십시오.
User
백엔드 용 및UserSmall
AJAX 호출 용을 가질 수 있습니다 . 하나에는 10 개의 속성이 있고 하나에는 3 개의 속성이 있습니다.
임시 쿼리 작업의 단점 :
- 많은 쿼리에서 본질적으로 동일한 데이터로 끝납니다. 예를 들어,를 사용하면 많은 통화에 대해
User
본질적으로 동일하게 작성select *
됩니다. 하나의 통화는 10 개의 필드 중 8 개를, 하나는 5 개의 10을 받고, 하나는 7의 10을받습니다. 이것이 나쁜 이유는 리팩터링 / 테스트 / 모의로 살인이기 때문입니다. - 시간이 지남에 따라 코드에 대해 높은 수준으로 추론하기가 매우 어려워집니다. "왜
User
그렇게 느린가?" 와 같은 진술 대신 일회성 쿼리를 추적하면 버그 수정이 작고 현지화되는 경향이 있습니다. - 기본 기술을 대체하기는 정말 어렵습니다. MySQL에 모든 것을 저장하고 MongoDB로 옮기려면 소수의 엔티티보다 100 개의 임시 호출을 교체하는 것이 훨씬 어렵습니다.
Q : 저장소에 너무 많은 메소드가 있습니다.
A : 통화 통합 이외의 다른 방법은 실제로 보지 못했습니다. 저장소의 메소드 호출은 실제로 애플리케이션의 기능에 맵핑됩니다. 더 많은 기능, 더 많은 데이터 특정 통화. 기능을 되돌리고 유사한 통화를 하나로 통합 할 수 있습니다.
하루가 끝날 때의 복잡성은 어딘가에 존재해야합니다. 리포지토리 패턴을 사용하여 저장 프로 시저를 만들지 않고 리포지토리 인터페이스로 푸시했습니다.
때때로 나는 "어딘가에 주어야했다!은 총알이 없다"고 스스로에게 말해야한다.
다음 인터페이스를 사용합니다.
Repository
-엔터티로드, 삽입, 업데이트 및 삭제Selector
-저장소에서 필터를 기반으로 엔티티를 찾습니다.Filter
-필터링 로직을 캡슐화
나는 Repository
데이터베이스에 구애받지 않는다. 실제로 지속성을 지정하지 않습니다. 그것이 무엇이든 될 수있다 : 능력을 검색하는 등 SQL 데이터베이스, XML 파일, 원격 서비스, 우주에서 외계인의 Repository
구축합니다 Selector
, 필터링 할 수 있습니다 LIMIT
-ed는, 분류 및 계산. 결국, 선택기 Entities
는 지속성에서 하나 이상을 가져옵니다 .
샘플 코드는 다음과 같습니다.
<?php
interface Repository
{
public function addEntity(Entity $entity);
public function updateEntity(Entity $entity);
public function removeEntity(Entity $entity);
/**
* @return Entity
*/
public function loadEntity($entityId);
public function factoryEntitySelector():Selector
}
interface Selector extends \Countable
{
public function count();
/**
* @return Entity[]
*/
public function fetchEntities();
/**
* @return Entity
*/
public function fetchEntity();
public function limit(...$limit);
public function filter(Filter $filter);
public function orderBy($column, $ascending = true);
public function removeFilter($filterName);
}
interface Filter
{
public function getFilterName();
}
그런 다음 하나의 구현 :
class SqlEntityRepository
{
...
public function factoryEntitySelector()
{
return new SqlSelector($this);
}
...
}
class SqlSelector implements Selector
{
...
private function adaptFilter(Filter $filter):SqlQueryFilter
{
return (new SqlSelectorFilterAdapter())->adaptFilter($filter);
}
...
}
class SqlSelectorFilterAdapter
{
public function adaptFilter(Filter $filter):SqlQueryFilter
{
$concreteClass = (new StringRebaser(
'Filter\\', 'SqlQueryFilter\\'))
->rebase(get_class($filter));
return new $concreteClass($filter);
}
}
이념은 일반적인 Selector
용도 Filter
이지만 구현시 SqlSelector
에는 SqlFilter
; 는 SqlSelectorFilterAdapter
일반적인 적응 Filter
콘크리트에 SqlFilter
.
클라이언트 코드는 Filter
객체 (일반 필터)를 생성하지만 선택기의 구체적인 구현에서는 이러한 필터가 SQL 필터로 변환됩니다.
와 같은 다른 선택기 구현은 그들의 특정 을 사용하여로 InMemorySelector
변환 한다. 따라서 모든 선택기 구현에는 자체 필터 어댑터가 제공됩니다.Filter
InMemoryFilter
InMemorySelectorFilterAdapter
이 전략을 사용하면 클라이언트 코드 (bussines 레이어)가 특정 리포지토리 또는 선택기 구현에 신경 쓰지 않습니다.
/** @var Repository $repository*/
$selector = $repository->factoryEntitySelector();
$selector->filter(new AttributeEquals('activated', 1))->limit(2)->orderBy('username');
$activatedUserCount = $selector->count(); // evaluates to 100, ignores the limit()
$activatedUsers = $selector->fetchEntities();
추신 : 이것은 내 실제 코드의 단순화입니다
나는 현재이 모든 것을 스스로 파악하려고 노력하면서 이것에 약간을 추가 할 것입니다.
# 1과 2
이것은 ORM이 무거운 물건을 들기에 완벽한 장소입니다. 어떤 종류의 ORM을 구현하는 모델을 사용하는 경우 이러한 방법을 사용하는 방법을 사용할 수 있습니다. 필요한 경우 Eloquent 메소드를 구현하는 고유 한 orderBy 함수를 작성하십시오. 예를 들어 Eloquent 사용 :
class DbUserRepository implements UserRepositoryInterface
{
public function findAll()
{
return User::all();
}
public function get(Array $columns)
{
return User::select($columns);
}
당신이 찾고있는 것은 ORM입니다. 리포지토리를 기반으로 할 수없는 이유는 없습니다. 이것은 사용자가 웅변 확장해야하지만 개인적으로는 문제가되지 않습니다.
그러나 ORM을 피하려면 원하는 것을 얻기 위해 "자신의 롤"을해야합니다.
#삼
인터페이스는 어렵고 빠른 요구 사항이 아닙니다. 무언가가 인터페이스를 구현하고 추가 할 수 있습니다. 할 수없는 것은 해당 인터페이스의 필수 기능을 구현하지 못하는 것입니다. 클래스와 같은 인터페이스를 확장하여 DRY를 유지할 수 있습니다.
즉, 나는 이제 막 이해하기 시작했지만 이러한 실현이 나를 도왔습니다.
우리가 (내 회사에서)이 문제를 처리하는 방식에 대해서만 언급 할 수 있습니다. 우선 모든 성능은 우리에게 큰 문제가 아니지만 깨끗하고 적절한 코드를 갖는 것입니다.
우선 UserModel
ORM을 사용하여 객체를 만드는 모델과 같은 모델을 정의 UserEntity
합니다. UserEntity
모델에서 a를로드 하면 모든 필드가로드됩니다. 외부 엔티티를 참조하는 필드의 경우 적절한 외부 모델을 사용하여 해당 엔티티를 작성합니다. 해당 엔티티의 경우 데이터가 요청시로드됩니다. 이제 당신의 초기 반응은 ... ??? ... !!! 예를 들어 보겠습니다.
class UserEntity extends PersistentEntity
{
public function getOrders()
{
$this->getField('orders'); //OrderModel creates OrderEntities with only the ID's set
}
}
class UserModel {
protected $orm;
public function findUsers(IGetOptions $options = null)
{
return $orm->getAllEntities(/*...*/); // Orm creates a list of UserEntities
}
}
class OrderEntity extends PersistentEntity {} // user your imagination
class OrderModel
{
public function findOrdersById(array $ids, IGetOptions $options = null)
{
//...
}
}
우리의 경우 $db
엔터티를로드 할 수있는 ORM입니다. 모델은 ORM에 특정 유형의 엔티티 세트를로드하도록 지시합니다. ORM은 맵핑을 포함하며이를 사용하여 해당 엔티티의 모든 필드를 엔티티에 주입합니다. 그러나 외부 필드의 경우 해당 오브젝트의 ID 만로드됩니다. 이 경우 , 참조 된 주문의 ID만으로를 OrderModel
작성 OrderEntity
합니다. 엔티티 PersistentEntity::getField
가 호출 하면 OrderEntity
모델에 모든 필드를 지연로드하도록 지시합니다 OrderEntity
. OrderEntity
하나의 UserEntity와 연관된 모든 항목이 하나의 결과 집합으로 처리되어 한 번에로드됩니다.
여기서 마술은 모델과 ORM이 모든 데이터를 엔티티에 주입하고 엔티티가에서 제공하는 일반 getField
메소드에 대한 래퍼 함수 만 제공한다는 것 PersistentEntity
입니다. 요약하면 항상 모든 필드를로드하지만 외부 엔티티를 참조하는 필드는 필요할 때로드됩니다. 여러 필드를로드하는 것만으로는 성능 문제가 아닙니다. 가능한 모든 외부 엔티티를로드하지만 성능이 크게 저하됩니다.
이제 where 절을 기반으로 특정 사용자 집합을로드합니다. 함께 붙일 수있는 간단한 표현을 지정할 수있는 객체 지향 클래스 패키지를 제공합니다. 예제 코드에서 나는 그것을 명명했다 GetOptions
. 선택 쿼리에 대한 모든 가능한 옵션을위한 래퍼입니다. 여기에는 where 절, group by 절 및 기타 모든 항목이 포함됩니다. 우리의 where 절은 매우 복잡하지만 분명히 더 간단한 버전을 쉽게 만들 수 있습니다.
$objOptions->getConditionHolder()->addConditionBind(
new ConditionBind(
new Condition('orderProduct.product', ICondition::OPERATOR_IS, $argObjProduct)
)
);
이 시스템의 가장 간단한 버전은 쿼리의 WHERE 부분을 문자열로 모델에 직접 전달하는 것입니다.
이 복잡한 답변에 대해 죄송합니다. 프레임 워크를 최대한 빠르고 명확하게 요약하려고했습니다. 추가 질문이 있으면 언제든지 문의하여 답변을 업데이트하겠습니다.
편집 : 또한 실제로 일부 필드를로드하지 않으려는 경우 ORM 매핑에서 지연로드 옵션을 지정할 수 있습니다. 모든 필드는 결국 getField
메소드를 통해 로드되므로 해당 메소드가 호출 될 때 몇 분 동안 일부 필드를로드 할 수 있습니다. 이것은 PHP에서 큰 문제는 아니지만 다른 시스템에는 권장하지 않습니다.
이것들은 내가 본 다른 솔루션입니다. 그들 각각에는 장단점이 있지만, 당신이 결정해야합니다.
이슈 # 1 : 너무 많은 필드
이는 인덱스 전용 스캔 을 고려할 때 특히 중요한 측면 입니다. 이 문제를 해결하기위한 두 가지 해결책이 있습니다. 반환 할 열 목록이 포함 된 선택적 배열 매개 변수를 사용하도록 함수를 업데이트 할 수 있습니다. 이 매개 변수가 비어 있으면 쿼리의 모든 열이 반환됩니다. 이것은 조금 이상 할 수 있습니다. 매개 변수를 기반으로 객체 또는 배열을 검색 할 수 있습니다. 동일한 쿼리를 실행하는 두 개의 고유 한 함수를 갖도록 모든 함수를 복제 할 수 있지만 하나는 열 배열을 반환하고 다른 하나는 개체를 반환합니다.
public function findColumnsById($id, array $columns = array()){
if (empty($columns)) {
// use *
}
}
public function findById($id) {
$data = $this->findColumnsById($id);
}
문제 # 2 : 너무 많은 방법
나는 1 년 전에 Propel ORM 과 함께 잠시 일 했고 이것은 그 경험에서 내가 기억할 수있는 것에 근거합니다. Propel에는 기존 데이터베이스 스키마를 기반으로 클래스 구조를 생성 할 수있는 옵션이 있습니다. 각 테이블에 대해 두 개의 오브젝트를 작성합니다. 첫 번째 객체는 현재 나열한 것과 비슷한 긴 액세스 기능 목록입니다. findByAttribute($attribute_value)
. 다음 객체는이 첫 번째 객체에서 상속됩니다. 이 하위 오브젝트를 업데이트하여 더 복잡한 getter 함수를 빌드 할 수 있습니다.
다른 솔루션은 __call()
정의되지 않은 함수를 실행 가능한 것으로 매핑 하는 데 사용 됩니다. 여러분의 __call
것입니다 방법은 다른 쿼리에 findById 메소드와 경우 FindByName을 구문 분석 할 수있을 것입니다.
public function __call($function, $arguments) {
if (strpos($function, 'findBy') === 0) {
$parameter = substr($function, 6, strlen($function));
// SELECT * FROM $this->table_name WHERE $parameter = $arguments[0]
}
}
나는 이것이 적어도 어떤 도움이되기를 바랍니다.
Laravel5에서 Repositories / Criterias 등을 구현하는 공급 업체로 https://packagist.org/packages/prettus/l5-repository 를 제안합니다 ...
@ ryan1234에 동의합니다. 코드 내에서 완전한 객체를 전달해야하며 일반적인 쿼리 메소드를 사용하여 해당 객체를 가져와야합니다.
Model::where(['attr1' => 'val1'])->get();
외부 / 엔드 포인트 사용의 경우 GraphQL 방법이 정말 좋습니다.
POST /api/graphql
{
query: {
Model(attr1: 'val1') {
attr2
attr3
}
}
}
참고 URL : https://stackoverflow.com/questions/16176990/proper-repository-pattern-design-in-php
'development' 카테고리의 다른 글
노드 0.12에`node-sass`를 다시 설치 하시겠습니까? (0) | 2020.03.31 |
---|---|
Blob URL은 무엇이며 왜 사용됩니까? (0) | 2020.03.31 |
Python 3으로 인쇄시 구문 오류 (0) | 2020.03.31 |
주석이 달린 태그와 주석이없는 태그의 차이점은 무엇입니까? (0) | 2020.03.31 |
생성자에서 예외 발생 (0) | 2020.03.31 |