我有一个问题,当对象可以通过多种方式创建时,最好的方法是什么。
假设我们有:
class User {
private $id;
private $name;
private $surname;
private $login;
public function setId($id) {
$id = (int) $id;
if (!$id) {
throw new Exception("Wrong ID");
}
$this->id = $id;
}
public function setName($name) {
$this->name = trim($name);
if (strlen($name) < 5) {
throw new Exception("Name is to short");
}
}
public function setSurname($surname) {
$this->surname = trim($surname);
if (strlen($surname) < 5) {
throw new Exception("Surname is to short");
}
}
public function setLogin($login) {
/* some login validation */
$this->login = $login;
}
/* and other methods */
}
不,我可以通过多种方式使用它:
1。创建新用户并将其保存在DB中:
$User = (new User)
->setName("John")
->setSurname("Kent")
->setLogin("JohnK");
$UserSaver = (new UserSaver($User))->save();
2。从DB加载用户
class UserFactory {
public function createById($userId, $db) {
$row = $db->GetRow("SELECT * FROM users WHERE u_id = {$db->qstr($userId}");
$User = (new User)
->setId($row['id'])
->setName($row['name'])
->setSurname($row['surname'])
->setLogin($row['login']);
return $User;
}
}
$User = UsersFactory::createByID(7, $db);
但我的问题是关于安装者的验证。应该在那吗?
当从数据库获取记录时,例如登录太短(由其他应用程序存储得更加严重)时,它可能会导致问题。然后会抛出Exception
。
我可以:
将验证移至单独的函数 $ this-&gt; validate()但我认为更好的做法是尽快检测错误的数据。
通过添加第二个参数来修改setter: function setLogin($ login,$ validate = true)然后在UsersFactory中执行(新用户) - &gt; setLogin($ row ['login '], false )
使用工厂的继承来跳过setter。
像这样:
class UserFromDB extends User () {
public function __construct($userId, $db) {
$row = $db->GetRow("SELECT * FROM users WHERE u_id = {$db->qstr($userId}");
$this->id = $row['id'];
$this->name = $row['name'];
$this->surname = $row['surname'];
$this->login = $row['login'];
}
}
我将非常感谢您寻求最佳解决方案。
答案 0 :(得分:1)
根据个人喜好,以下是一些选项。
您的数据库是否包含不符合您的政策的数据?如果是,那么为什么不强制执行该策略并清理您的数据库。检测所有不符合策略的数据并提出一些迁移计划。这样可以解决您的问题并同时清理您的数据。如果由于某种原因你不想这样做,你可能想问自己,你的政策是否真的有用。
假设上述情况不可能,我建议采用以下两种方法之一:
Command Query Responsibility Separation模式是一种非常干净的方法来避免这些问题。基本思想是创建一个用于数据库写入的类和一个用于数据库读取的类。这意味着您必须在用于读取的现有用户类旁边创建另一个类(UserReadModel将是CQRS术语中的适当名称)。
CQRS允许您编写健壮的(域驱动的)代码。例如,而不是具有整数id。您可以创建一个只能保存有效ID的Id类(例如正整数)。而不是传递用户名的整数id,你可以传递一个Id id。然后,您不再需要在聚合级别验证或使用单独的验证器,也不需要无效的Id。因此,其他类可以从相同的验证中受益。传递Id而不是整数,您的代码不会出错。当然,你的阅读模型仍然可以使用整数。
如果您想了解更多信息,请阅读有关域驱动设计,不可变类和CQRS的信息。正确组合的这些技术可以成为非常健壮的代码的基础。
你的第一个建议。这种方法允许您保留代码,但两种方式都有。但问题是它不会强制类的用户调用validate方法。您当然可以强制您的数据库层在持久性之前始终在所有类上调用此方法,但通常在此过程中有点太晚了。此外,您需要某种方法从验证错误中推断出哪些字段存在错误。
如果在一个属性上跳过验证而在下一个属性上没有验证,则验证参数方法允许您的对象变为半有效。因此,您仍然需要一个验证方法来确保该类完全有效。因此,它是同一方法的变体。这种方法还有另外一个问题:你不能确保所有的setter方法(将来)都有这个布尔参数。这使得很难概括。
执行此操作的一种简洁方法是将用户验证程序类注入到用户类中。然后,validate方法将验证委托给验证器类。要确保用户类始终具有验证器,而不是使用new关键字直接创建用户类,您可以使用工厂使用适当的验证器类创建用户类。对于数据库选择,您可以拥有严格的数据库插入和更新验证器以及弱验证器或根本没有验证器。这种方法的一个优点是这种模式可以重用于其他类。
如果您希望在调用setter时进行验证,则可以创建验证器的集合。这意味着您可以将“名称验证器”绑定到“用户验证器”中的姓氏和名字属性。因此,用户验证器将成为字段验证器的集合。这将允许您在设定的时刻触发特定字段或输入的验证。