前一段时间我问了一个类似的问题:Using the Data Mapper Pattern, Should the Entities (Domain Objects) know about the Mapper?但是,它是通用的,我真的很感兴趣如何用Doctrine2专门完成一些事情。
以下是一个简单的示例模型:每个Thing
可以来自Vote
User
,User
可以投放多个Vote
但只有Vote
最后Msssage
计数。由于其他数据(Vote
等)与Vote
相关,因此在放置第二个Vote
时,原始Thing
无法更新,因此需要替换。
目前public function addVote($vote)
{
$vote->entity = $this;
}
具有此功能:
Vote
public function setThing(Model_Thing $thing)
{
$this->thing = $thing;
$thing->votes[] = $this;
}
负责建立关系:
User
在我看来,确保Vote
仅计算最后Thing
Thing
应该确保的内容,not some service layer。
所以要在模型中保留新的public function addVote($vote)
{
foreach($this->votes as $v){
if($v->user === $vote->user){
//remove vote
}
}
$vote->entity = $this;
}
函数:
Vote
那么如何从域模型中删除Vote::setThing()
?我应该放松NULL
接受Thing
吗?我是否应该使用foreach
可用于删除投票的某种服务层?一旦投票开始累积,Thing
将会变慢 - 如果使用服务层允许Vote
搜索{{1}}而无需加载整个集合吗?
我绝对倾向于使用轻型服务层;但是,是否有更好的方法来处理Doctrine2的这类事情,或者我是朝着正确的方向前进?
答案 0 :(得分:7)
我投票支持服务层。我经常努力尝试在实体本身添加尽可能多的逻辑,并且只是让自己感到沮丧。无法访问EntityManager,您根本无法执行查询逻辑,当您只需要几条记录时,您会发现自己使用了大量的O(n)操作或延迟加载整个关系集(这是超级的与DQL提供的所有优势相比,我感到茫然。
如果您需要一些帮助来克服贫血领域模型始终是反模式的想法,请参阅Matthew Weier O'Phinney的this presentation或this question。
虽然我可能误解了术语,但我并不完全相信实体必须是域模型中允许的唯一对象。我很容易认为实体对象及其服务的总和构成了模型。我认为,当您最终编写一个几乎不关注关注点分离的服务层时,就会出现反模式。
我经常想到让所有实体对象将一些方法代理到服务层:
public function addVote($vote)
{
$this->_service->addVoteToThing($vote, $thing);
}
然而,由于Doctrine没有任何类型的对象水合回调事件系统,我还没有找到一种优雅的方式来注入服务对象。
答案 1 :(得分:6)
我的建议是将所有查询逻辑放入EntityRepository,然后从中创建一个类似的接口:
class BlogPostRepository extends EntityRepository implements IBlogPostRepository {}
通过这种方式,您可以在服务对象的单元测试中使用接口,并且不需要依赖EntityManager。