PHP中模型验证的最佳方法?

时间:2013-03-04 19:29:30

标签: php validation model

我了解到,通常有很多方法可以解决一个编程问题,每种方法通常都有其自身的好处和负面影响。

我今天要确定的是在PHP中进行模型验证的最佳方法。使用一个人的例子,我概述了我过去使用的四种不同的方法,每种方法包括类和一个用法示例,以及我喜欢和不喜欢的每种方法。

我的问题是:您觉得哪种方法最好?或者你有更好的方法吗?

方法#1:使用模型类

中的setter方法进行验证

好的

  • 简单,只有一个班级
  • 通过抛出异常,该类永远不会处于无效状态(除了业务逻辑,即出生前死亡)
  • 不必记得调用任何验证方法

  • 只能返回1个错误(通过Exception
  • 需要使用异常并捕获它们,即使错误不是很特殊
  • 只能对一个参数执行操作,因为其他参数可能尚未设置(无法比较birth_datedeath_date
  • 由于大量验证,模型类可能很长
class Person
{
    public $name;
    public $birth_date;
    public $death_date;

    public function set_name($name)
    {
        if (!is_string($name))
        {
            throw new Exception('Not a string.');
        }

        $this->name = $name;
    }

    public function set_birth_date($birth_date)
    {
        if (!is_string($birth_date))
        {
            throw new Exception('Not a string.');
        }

        if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $birth_date))
        {
            throw new Exception('Not a valid date.');
        }

        $this->birth_date = $birth_date;
    }

    public function set_death_date($death_date)
    {
        if (!is_string($death_date))
        {
            throw new Exception('Not a string.');
        }

        if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $death_date))
        {
            throw new Exception('Not a valid date.');
        }

        $this->death_date = $death_date;
    }
}
// Usage:

try
{
    $person = new Person();
    $person->set_name('John');
    $person->set_birth_date('1930-01-01');
    $person->set_death_date('2010-06-06');
}
catch (Exception $exception)
{
    // Handle error with $exception
}

方法#2:使用模型类

中的验证方法进行验证

好的

  • 简单,只有一个班级
  • 可以验证(比较)多个参数(因为在设置所有模型参数后进行验证)
  • 可以返回多个错误(通过errors()方法)
  • 免于例外
  • 让getter和setter方法可用于其他任务

  • 模型可能处于无效状态
  • 开发者必须记得调用验证is_valid()方法
  • 由于大量验证,模型类可能很长
class Person
{
    public $name;
    public $birth_date;
    public $death_date;

    private $errors;

    public function errors()
    {
        return $this->errors;
    }

    public function is_valid()
    {
        $this->validate_name();
        $this->validate_birth_date();
        $this->validate_death_date();

        return count($this->errors) === 0;
    }

    private function validate_name()
    {
        if (!is_string($this->name))
        {
            $this->errors['name'] = 'Not a string.';
        }
    }

    private function validate_birth_date()
    {
        if (!is_string($this->birth_date))
        {
            $this->errors['birth_date'] = 'Not a string.';
            break;
        }

        if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->birth_date))
        {
            $this->errors['birth_date'] = 'Not a valid date.';
        }
    }

    private function validate_death_date()
    {
        if (!is_string($this->death_date))
        {
            $this->errors['death_date'] = 'Not a string.';
            break;
        }

        if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->death_date))
        {
            $this->errors['death_date'] = 'Not a valid date.';
            break;
        }

        if ($this->death_date < $this->birth_date)
        {
            $this->errors['death_date'] = 'Death cannot occur before birth';
        }
    }
}
// Usage:

$person = new Person();
$person->name = 'John';
$person->birth_date = '1930-01-01';
$person->death_date = '2010-06-06';

if (!$person->is_valid())
{
    // Handle errors with $person->errors()
}

方法#3:单独验证类中的验证

好的

  • 非常简单的模型(所有验证都在单独的类中进行)
  • 可以验证(比较)多个参数(因为在设置所有模型参数后进行验证)
  • 可以返回多个错误(通过errors()方法)
  • 免于例外
  • 让getter和setter方法可用于其他任务

  • 稍微复杂一点,因为每个模型需要两个类
  • 模型可能处于无效状态
  • 开发人员必须记住使用验证类
class Person
{
    public $name;
    public $birth_date;
    public $death_date;
}
class Person_Validator
{
    private $person;
    private $errors = array();

    public function __construct(Person $person)
    {
        $this->person = $person;
    }

    public function errors()
    {
        return $this->errors;
    }

    public function is_valid()
    {
        $this->validate_name();
        $this->validate_birth_date();
        $this->validate_death_date();

        return count($this->errors) === 0;
    }

    private function validate_name()
    {
        if (!is_string($this->person->name))
        {
            $this->errors['name'] = 'Not a string.';
        }
    }

    private function validate_birth_date()
    {
        if (!is_string($this->person->birth_date))
        {
            $this->errors['birth_date'] = 'Not a string.';
            break;
        }

        if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->person->birth_date))
        {
            $this->errors['birth_date'] = 'Not a valid date.';
        }
    }

    private function validate_death_date()
    {
        if (!is_string($this->person->death_date))
        {
            $this->errors['death_date'] = 'Not a string.';
            break;
        }

        if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->person->death_date))
        {
            $this->errors['death_date'] = 'Not a valid date.';
            break;
        }

        if ($this->person->death_date < $this->person->birth_date)
        {
            $this->errors['death_date'] = 'Death cannot occur before birth';
        }
    }
}
// Usage:

$person = new Person();
$person->name = 'John';
$person->birth_date = '1930-01-01';
$person->death_date = '2010-06-06';

$validator = new Person_Validator($person);

if (!$validator->is_valid())
{
    // Handle errors with $validator->errors()
}

方法#4:模型类和验证类中的验证

好的

  • 通过抛出异常,该类永远不会处于无效状态(除了业务逻辑,即出生前死亡)
  • 可以验证(比较)多个参数(因为在设置了所有模型参数后进行业务验证)
  • 可以返回多个错误(通过errors()方法)
  • 验证分为两组:类型(模型类)和业务(验证类)
  • 让getter和setter方法可用于其他任务

  • 如果抛出异常(模型类)和错误数组(验证类),错误处理会更复杂
  • 稍微复杂一点,因为每个模型需要两个类
  • 开发人员必须记住使用验证类
class Person
{
    public $name;
    public $birth_date;
    public $death_date;

    private function validate_name()
    {
        if (!is_string($this->person->name))
        {
            $this->errors['name'] = 'Not a string.';
        }
    }

    private function validate_birth_date()
    {
        if (!is_string($this->person->birth_date))
        {
            $this->errors['birth_date'] = 'Not a string.';
            break;
        }

        if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->person->birth_date))
        {
            $this->errors['birth_date'] = 'Not a valid date.';          
        }
    }

    private function validate_death_date()
    {
        if (!is_string($this->person->death_date))
        {
            $this->errors['death_date'] = 'Not a string.';
            break;
        }

        if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->person->death_date))
        {
            $this->errors['death_date'] = 'Not a valid date.';
        }
    }
}
class Person_Validator
{
    private $person;
    private $errors = array();

    public function __construct(Person $person)
    {
        $this->person = $person;
    }

    public function errors()
    {
        return $this->errors;
    }

    public function is_valid()
    {
        $this->validate_death_date();

        return count($this->errors) === 0;
    }

    private function validate_death_date()
    {
        if ($this->person->death_date < $this->person->birth_date)
        {
            $this->errors['death_date'] = 'Death cannot occur before birth';
        }
    }
}
// Usage:

try
{
    $person = new Person();
    $person->set_name('John');
    $person->set_birth_date('1930-01-01');
    $person->set_death_date('2010-06-06');

    $validator = new Person_Validator($person);

    if (!$validator->is_valid())
    {
        // Handle errors with $validator->errors()
    }
}
catch (Exception $exception)
{
    // Handle error with $exception
}

1 个答案:

答案 0 :(得分:2)

我认为只有一种最好的方法,这取决于你将如何使用你的课程。在这种情况下,当您只有一个简单的数据对象时,我更倾向于使用方法#2:使用模型类中的验证方法进行验证

在我看来,糟糕的事情并不是那么糟糕:

  

模型可能处于无效状态

有时希望能够使模型处于无效状态。

例如,如果从Web表单填充Person对象并想要记录它。如果使用第一种方法,则必须扩展Person类,覆盖所有setter以捕获异常,然后您就可以将此对象置于无效状态以进行日志记录。

  

开发人员必须记得调用验证is_valid()方法

如果模型绝对不能处于无效状态,或者方法要求模型处于有效状态,则可以始终从类中调用is_valid()以确保它处于有效状态。

  

由于大量验证,模型类可能很长

验证码仍必须在某处。大多数编辑器都允许您折叠功能,以便在阅读代码时不会出现问题。如果有的话,我认为在一个地方进行所有验证是件好事。