2011-03-31 64 views
1

我的大脑开始受到伤害,所以我决定在这里问问。验证入境或保存之前?

我有一个数据对象Employee。 Getters,setter,格式化程序等我有一个管理器EmployeeManager,它负责处理数据库访问和其他事情。现在我在EmployeeManager中有一个很大的验证块,但我一直在想我是否可以将其中的一些移动到setters。

例如,现在我有;

public function getSSN($bFormatted = true) { 
    return ($bFormatted) ? $this->format_ssn($this->SSN) : $this->SSN; 
} 
public function setSSN($s, $bValidate = false) 
{ 
    // If we're validating user entry, save a copy. 
    // Either way, store a trimmed version. 
    if ($bValidate): $this->SSNToValidate = $s; endif; 
    $this->SSN = str_replace('-', '', $s); 
} 
public function getSSNToValidate() { return $this->SSNToValidate; } 

这样做是:
*当你设置SSN,如果它被系统(如从数据库)来完成,然后它setSSN('123456789', false),由于社会安全号码存储在数据库SANS破折号。
*当您从用户输入设置SSN时,它只是简单地setSSN('123-45-6789')然后不仅修剪破折号,而且还存储原始版本以验证(因为我想根据格式进行验证)
*当您获得SSN时,如果请求格式化(并且它总是在写入数据库时​​除外),它将根据Employee类中的另一个函数进行格式化。

所以我的问题是:我可以在这里添加验证到setter,而不是依靠Manager类中的单片验证功能?因为我开始必须处理来自整个应用程序的错误,所以我决定暂时转移到一个中央的Error Handler静态类,而不是每个管理者维护它自己的错误列表。

的,因为这一点,我可以很容易地添加错误处理到这一点:

public function setSSN($s, $bFromUser = false) 
{ 
    if ($bFromUser && !$this->validateSSN($s)) 
    { 
     ErrorHandler::add(array('ssn' => 'Invalid SSN entered')); 
    } 
    else 
    { 
     $this->SSN = str_replace('-', '', $s); 
    } 
} 

所以我想我的问题是:在所有这是否有道理还是我从管理者移动验证冲水自己(要在需要时还是在写入数据库之前执行)到对象(在输入时执行)?

这是整体通用的,我只是使用SSN作为一个很好的例子。

回答

3

您应该始终验证何时在对象中创建对象或设置值。这样你总是保证有一个处于有效状态的对象。如果验证不在对象本身中,则不能保证该对象将被验证。

实施例的验证物体的外面:

在这种情况下有一个错误,我们忘了验证SSN。

class EmployeeManager 
{ 
    function saveEmployee($employee) 
    { 
     //Oops, we forgot to validate SSN 
     $db->save($employee); //This is just SQL to persist the employee. 
    } 
} 

//Somewhere else in code... 
$employee = new Employee(); 
$employee->setSSN("FredFlintstone"); //No validation is done here. 
$employeeManager = new EmployeeManager(); 
$employeeManager->saveEmployee($employee); //This call will persist FredFlintstone because validation was missed. 

实施例的验证物体的内部:

在这种情况下,雇员对象验证它的所有输入。这样我们就知道如果我们有一个雇员的实例,那么其中的所有数据都是有效的。

class Employee 
{ 
    function setSSN($input) 
    { 
     if(strlen($input) != 9) 
     { 
      throw new Exception('Invalid SSN.'); 
     } 
     //Other validations... 
     $this->ssn = $input; 
    } 
} 

//Somewhere else in code... 
$employee = new Employee(); 
$employee->setSSN("FredFlintstone"); //This call will now throw an exception and prevent us from having a bad object. 
$employeeManager = new EmployeeManager(); 
$employeeManager->saveEmployee($employee); 

此外你不应该让一个对象被创建,不完全初始化。如果说员工需要SSN,那么不要提供空构造函数。你的构造函数应该有所有必填字段的参数(与我为了清晰起见而忽略它们的例子不同)。

+0

是的,我看到其他人说,保持对象的完整性是至关重要的,这意味着不要在其中存储流浪数据。如果您尝试添加bum数据,请拒绝它。虽然我也看到了bum用户输入(这是)并不例外,但它是预期的,因此您应该毫无例外地处理它。但我认为,这是更多的语义和编程实践,而不是这个问题。 – Andrew 2011-03-31 21:38:38

+0

我同意处理用户数据会很痛苦。在这种情况下,我通常最终会进行两次验证:在UI中一次,所以我可以通知他们这个问题,然后再次在对象中。我觉得确保对象是正确的并且验证可能被调用两次比让我最终存储不良数据的可能性更安全。 – brainimus 2011-03-31 21:45:21

+0

将这个标记为答案,因为我已经去了“永远不要让你的对象处于有效状态”的想法。例如,如果我知道SSN将永远不会被设置为无效数字(因为它在设置上得到验证),那么我知道我不一定需要稍后再验证它。我必须学会信任代码。 – Andrew 2011-04-06 12:57:23

2

基于服务的应用程序的一般经验法则是验证应始终在服务器端发生10,在持久性之前不久。或者,您可以在客户端上执行验证以获得更好的用户体验。但仅在客户端执行验证(即在您的setter中)并不安全。永远不要依赖客户数据是正确的。

在进行基于客户端的验证时,最好为每个类(无论是模型,视图模型,演示者等,取决于您的架构模式)进行自我验证,而不是依赖某个外部验证程序。

+0

所以可能在'客户端'(数据对象)以及插入数据库之前在管理器中进行验证。我认为我的担心是,我最终会得到两个ValidateSSN()命令(一个在数据对象中,一个在管理器中),或者我将它外包给一个中央验证类...在这里你是说,不要依赖外部的验证器?或者我误解了? – Andrew 2011-03-31 20:47:15

+1

验证在这方面总是有点棘手 - 在DRO,SRP等各种OO原则之间进行权衡,将相关数据和行为保持在一起等等。所以回答这个问题并不简单。在客户端和服务器之间看到验证逻辑有些重复(通常客户端对于快速和脏验证有更简单的规则)并不罕见。 – 2011-03-31 20:49:41