【问题标题】:Best approach to model validation in PHP? [closed]PHP中模型验证的最佳方法? [关闭]
【发布时间】:2013-03-04 19:29:29
【问题描述】:

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

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

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

方法 #1:在模型类中使用 setter 方法进行验证

好的

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

不好的

  • 只能返回 1 个错误(通过 Exception
  • 需要使用异常并捕获它们,即使错误不是很异常
  • 只能作用于一个参数,因为其他参数可能尚未设置(无法比较 birth_datedeath_date
  • 由于大量验证,模型类可能很长​​li>
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()方法
  • 由于大量验证,模型类可能很长​​li>
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
}

【问题讨论】:

  • 哇,你真的可以做到这一点,然后继续前进 10 倍于它所花费的时间,我猜这不适合真正的工作情况。
  • @ChrisCooney 这是一篇很好的帖子,提出了一个很好的问题。 +1
  • @AmazingDreams string typehint in php?!
  • 我个人喜欢方法二,可能是因为我真的很喜欢 Yii 框架,基本上他们就是这样做的。唯一需要注意的是,您很少需要调用 is_valid()validate() 或任何您称之为的名称,因为它内置在类的​​ beforeSave() 部分中。

标签: php validation model


【解决方案1】:

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

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

模型可能处于无效状态

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

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

开发者一定要记得调用验证 is_valid() 方法

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

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

验证码必须仍然在某个地方。大多数编辑器允许您折叠函数,这样在阅读代码时就不会出现问题。如果有的话,我认为将所有验证集中在一个地方很好。

【讨论】:

  • 好点,我同意,负面也不是坏事。真正的选项#2 和#3 是相同的,只是将验证移到一个单独的类中......这是否有任何好处将是个人选择。选项 #2 还比 #1 有额外的好处,因为 getter/setter 可以免费用于其他用途。感谢您的回答!
  • 我认为在大多数情况下这不是一个好的解决方案,因为您将验证部分与其他“愚蠢”模型本身联系起来。在另一个上下文中,模型可以对“有效”有不同的定义。例如,如果您检查一个国家/地区的邮政编码,它可能会遵循其他国家/地区的其他规则,使用此解决方案您完全不灵活。验证模型的单独机制将这种责任分离。在我看来,类本身不应该包含任何验证逻辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-07
  • 2016-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-22
相关资源
最近更新 更多