eoi*_*noc 6 php exception-handling datamapper domain-model
在应用Data Mapper模式时,模型(在我的案例中为Domain Model)负责业务逻辑,而不是将实体保存到数据库的映射器.
构建一个单独的业务逻辑验证器来处理模型之外的用户提供的数据似乎是否合理?
下面是PHP语法中的一个例子.
假设我们有一个实体$person.假设该实体具有一个surname在保存时不能为空的属性.
用户输入了非法的空值surname.由于模型负责封装业务逻辑,$person->surname = $surname;因此当用户输入的$surname是空字符串时,我希望以某种方式说操作不成功.
在我看来,$person如果我们试图用非法值填充其中一个属性,那么应该抛出异常.
但是,从我读过的例外情况来看, "输入'坏'输入的用户也不例外:这是预期的." 其含义是不依赖异常来验证用户数据.
您如何建议解决此问题,在让域模型定义业务逻辑之间取得平衡,而不是依赖于域模型在填写用户输入数据时抛出的异常?
域模型不一定是可以直接转换为数据库行的对象.您的Person示例适合此描述,我喜欢将此类对象称为实体(从Doctrine 2 ORM中采用).但是,就像Martin Fowler所描述的那样,域模型是包含行为和数据的任何对象.
这是您描述的问题的一个非常严格的解决方案:
假设您的Person域模型(或实体)必须具有名字和姓氏,以及可选的姓氏.这些必须是字符串,但为简单起见,可能包含任何字符.您希望强制执行此类Person存在时,满足这些先决条件.这个类看起来像这样:
class Person
{
/**
* @var string
*/
protected $firstname;
/**
* @var string
*/
protected $lastname;
/**
* @var string|null
*/
protected $maidenname;
/**
* @param string $firstname
* @param string $lastname
* @param string|null $maidenname
*/
public function __construct($firstname, $lastname, $maidenname = null)
{
$this->setFirstname($firstname);
$this->setLastname($lastname);
$this->setMaidenname($maidenname);
}
/**
* @param string $firstname
*/
public function setFirstname($firstname)
{
if (!is_string($firstname)) {
throw new InvalidArgumentException('Must be a string');
}
$this->firstname = $firstname;
}
/**
* @return string
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* @param string $lastname
*/
public function setLastname($lastname)
{
if (!is_string($lastname)) {
throw new InvalidArgumentException('Must be a string');
}
$this->lastname = $lastname;
}
/**
* @return string
*/
public function getLastname()
{
return $this->lastname;
}
/**
* @param string|null $maidenname
*/
public function setMaidenname($maidenname)
{
if (!is_string($maidenname) or !is_null($maidenname)) {
throw new InvalidArgumentException('Must be a string or null');
}
$this->maidenname = $maidenname;
}
/**
* @return string|null
*/
public function getMaidenname()
{
return $this->maidenname;
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,没有办法(无视Reflection)您可以Person在不满足先决条件的情况下实例化对象.这是一件好事,因为无论何时遇到Person对象,您都可以100%确定您正在处理的数据类型.
现在您需要第二个域模型来处理用户输入,让我们调用它PersonForm(因为它通常代表在网站上填写的表单).它具有相同的属性Person,但盲目接受任何类型的数据.它还有一个验证规则列表,一个isValid()使用这些规则来验证数据的方法,以及一个获取任何违规的方法.我将把课程的定义留给你的想象:)
最后,您需要一个控制器(或服务)将这些绑定在一起.这是一些伪代码:
class PersonController
{
/**
* @param Request $request
* @param PersonMapper $mapper
* @param ViewRenderer $view
*/
public function createAction($request, $mapper, $view)
{
if ($request->isPost()) {
$data = $request->getPostData();
$personForm = new PersonForm();
$personForm->setData($data);
if ($personForm->isValid()) {
$person = new Person(
$personForm->getFirstname(),
$personForm->getLastname(),
$personForm->getMaidenname()
);
$mapper->insert($person);
// redirect
} else {
$view->setErrors($personForm->getViolations());
$view->setData($data);
}
}
$view->render('create/add');
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,PersonForm它用于拦截和验证用户输入.只有当输入有效时,Person才会创建并保存在数据库中.
这确实意味着某些业务逻辑将被复制:
在Person你想要强制执行业务规则,但它可以简单地在某些东西关闭时抛出异常.
在PersonForm你必须适用相同的规则,防止到达无效的用户输入验证Person.但是这些验证器可以更先进.想想人类错误消息,破坏第一条规则等等.你也可以应用稍微改变输入的过滤器(例如小写用户名).
换句话说:Person将在较低级别强制执行业务规则,而PersonForm更多是关于处理用户输入.
一种不太严格的方法,但可能更方便:
限制完成的验证Person以强制执行所需的属性,并强制执行属性类型(字符串,整数等).不仅如此.
您还可以在其中列出约束Person.这些是业务规则,但没有实际的验证代码.所以这只是一些配置.
拥有Validator能够接收数据以及约束列表的服务.它应该能够根据约束验证数据.对于每种类型的约束,您可能需要一个小的验证器类.(看看Symfony 2验证器组件).
PersonForm可以Validator注入服务,因此可以使用该服务来验证用户输入.
最后,有一项PersonManager服务负责您要执行的任何操作Person(如创建/更新/删除,以及注册/激活/等等).在PersonManager将需要PersonMapper的依赖.
当你需要创建一个时Person,你会调用类似的东西$personManager->create($userInput);.调用会创建一个PersonForm,验证数据,创建一个Person(当数据有效时),并坚持Person使用PersonMapper.
关键在于:
您可以围绕所有这些类绘制一个圆圈,并将其称为"人员域"(DDD).并且该域的接口(入口点)是PersonManager服务.您要执行的每个操作都Person 必须通过PersonManager.
如果你坚持在你的应用程序中,你应该是安全的,以确保业务规则:)
| 归档时间: |
|
| 查看次数: |
655 次 |
| 最近记录: |