Php 使用通知模式进行域验证
历史上,我在对象的构造函数中执行过对象验证,当验证失败时抛出异常。例如:Php 使用通知模式进行域验证,php,validation,design-patterns,domain-driven-design,Php,Validation,Design Patterns,Domain Driven Design,历史上,我在对象的构造函数中执行过对象验证,当验证失败时抛出异常。例如: class Name { const MIN_LENGTH = 1; const MAX_LENGTH = 120; private $value; public function __construct(string $name) { if (!$this->isValidNameLength($name)) { throw ne
class Name
{
const MIN_LENGTH = 1;
const MAX_LENGTH = 120;
private $value;
public function __construct(string $name)
{
if (!$this->isValidNameLength($name)) {
throw new InvalidArgumentException(
sprintf('The name must be between %d and %d characters long', self::MIN_LENGTH, self::MAX_LENGTH)
);
}
$this->value = $name;
}
public function changeName(string $name)
{
return new self($name);
}
private function isValidNameLength(string $name)
{
return strlen($name) >= self::MIN_LENGTH && strlen($name) <= self::MAX_LENGTH;
}
}
class Room
{
private $name;
private $description;
public function __construct(Name $name, Description $description)
{
$this->name = $name;
$this->description = $description;
}
}
class Name
{
public function __construct(string $name)
{
// do some validation
}
}
class Description
{
public function __construct(string $description)
{
// do some validation
}
}
如果Name
和Description
都未能通过验证,我希望能够返回两个对象的失败消息,而不仅仅是第一个失败的对象的单一异常
在阅读了一些有关的文章后,我觉得这将非常适合我的场景。我陷入困境的地方是如何执行验证,以及在验证失败时如何防止对象进入无效状态
class Name
{
const MIN_LENGTH = 1;
const MAX_LENGTH = 120;
private $notification;
private $value;
public function __construct(string $name, Notification $notification)
{
$this->notification = $notification;
$this->setName($name);
}
private function setName(string $name)
{
if ($this->isValidNameLength($name)) {
$this->value = $name;
}
}
private function isValidNameLength(string $name)
{
if (strlen($name) < self::MIN_LENGTH || strlen($name) > self::MAX_LENGTH) {
$this->notification->addError('NAME_LENGTH_INVALID');
return false;
}
return true;
}
public function hasError()
{
return $this->notification->hasError();
}
public function getError()
{
return $this->notification->getError();
}
}
类名
{
常数最小长度=1;
const MAX_LENGTH=120;
私人通知;
私人美元价值;
公共函数构造(字符串$name,通知$Notification)
{
$this->notification=$notification;
$this->setName($name);
}
私有函数setName(字符串$name)
{
如果($this->isValidNameLength($name)){
$this->value=$name;
}
}
私有函数isValidNameLength(字符串$name)
{
if(strlen($name)self::MAX_LENGTH){
$this->notification->addError('NAME_LENGTH_INVALID');
返回false;
}
返回true;
}
公共函数hasError()
{
返回$this->notification->hasrerror();
}
公共函数getError()
{
返回$this->notification->getError();
}
}
我对上述问题有几点担心:
$value
为null
,这不是有效状态名称
后,我必须记得调用hasError
,以确定是否发生了验证错误hasError
/getError
函数来标记我的域对象,我不确定这是否是一个好的实践这个谜题有我遗漏的一块吗?我如何利用通知模式,但确保我的对象不能进入无效状态?调用
通知.hasError
函数,然后抛出异常如何
通过这种方式,您可以使用通知处理来处理任何错误,并且由于存在异常,可以保证您没有有效的对象。好吧,您几乎已经这样做了,但是如果您在一个validate()方法中共享所有验证,那么您可以在继续业务逻辑之前调用它 大概是
if (model.validate())
{
// You can safely proceed
}
After this you can for example throw an exception so you will know that you have an object in an invalid state.
我如何利用通知模式,但确保我的对象不能进入无效状态
工厂模式-又名“命名构造函数”
您离开的构造函数验证是——您永远不想创建一个不能保留其自身不变量的值
但是您将构造函数从公共API中移除,而不是安排客户端代码调用工厂中的方法。该工厂将决定如何管理构建对象的失败-收集所有的
- 正在引发包含通知集合的异常
- 返回包含通知集合的故障类型
取决于您是喜欢处理带有异常的控制流还是有区别的联合。我还喜欢提供错误列表(在您的案例通知中),而不是立即抛出异常。但是我仍然想确保我的域模型不能进入无效状态 我这里的经验法则是,如果用户可能犯了一个错误(或一系列错误),仅当您作为程序员显然犯了一个错误或系统中发生了其他不好的事情(由于用户输入而没有发生)时才会抛出异常 所以我遵循的模式是基于Martin Fowler()和Vladimir Khorikov()的想法 每当操作或构造函数执行可能导致异常的必要验证时,提供相应的方法询问实体是否可以执行该操作 这意味着,如果要检查任何只有实体知道的特定于域的验证逻辑,则程序员在执行操作或构造域实体之前应使用询问方法(例如canPurchaseProduct()) 如果asking方法失败,您可以从聚合根目录收集所有错误,并将错误告知用户 实体(或构造函数)执行实际工作的相应操作也将调用asking方法,如果调用asking方法导致任何错误,它将抛出异常。例如,包含所有错误的摘要 当然,每个实体都必须遵循这个模式,这样每个实体都可以在需要执行自己的操作时调用其子实体的asking方法 我在这里使用的是良好的旧订单和订单项示例 我使用的抽象基类为所有域实体类提供错误处理功能
抽象类AbstractDomainEntity
{
公共函数加法器(字符串$message)
{
//...
}
公共函数合并错误(数组$errors)
{
//...
}
公共函数getErrors():数组
{
//...
}
公共函数getErrorSummary():数组
{
//...
}
公共函数hasErrors():bool
{
//...
}
}
Order类扩展了抽象域类以使用其错误处理功能
类顺序扩展了AbstractDomainEntity
{
公共函数构造(OrderId$id,CustomerId$CustomerId,ShopId$ShopId,$orderItems)
{
$this->canCreate($id、$customerId、$shopId、,