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

Jon*_*han 35 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;
    }
}
Run Code Online (Sandbox Code Playgroud)
// 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
}
Run Code Online (Sandbox Code Playgroud)

方法#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';
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
// 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()
}
Run Code Online (Sandbox Code Playgroud)

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

好的

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

坏的

  • 稍微复杂一点,因为每个模型都需要两个类
  • 模型可能处于无效状态
  • 开发人员必须记住使用验证类
class Person
{
    public $name;
    public $birth_date;
    public $death_date;
}
Run Code Online (Sandbox Code Playgroud)
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';
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
// 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()
}
Run Code Online (Sandbox Code Playgroud)

方法#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.';
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
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';
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
// 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
}
Run Code Online (Sandbox Code Playgroud)

mak*_*mak 2

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

在我看来,坏事并没有那么坏:

模型可能处于无效状态

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

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

开发者必须记住调用验证 is_valid() 方法

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

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

验证码仍然必须到达某处。大多数编辑器允许您折叠函数,这样在阅读代码时就不会有问题。如果有什么不同的话,我认为将所有验证集中在一处是件好事。