【问题标题】:OOP form validation without using multiple return types不使用多种返回类型的 OOP 表单验证
【发布时间】:2013-02-27 20:05:11
【问题描述】:

我有一个用户可以登录的程序。我有一个User 类,当他们登录时,他们的所有属性(权限、用户名、真实姓名等)都保存为类属性。但是,由于所有数据都需要验证,我很难确定让用户更新他们自己的信息的最佳方式。

目前,当用户尝试更新自己的信息时,例如他们的电子邮件地址,我愿意:

// Set new email
$try = $user->setEmail($_POST['new_email']);
if ($try !== true) {
    $errors[] = $try;
}
...
if (isset($errors) {
    // Tell user what went wrong
} else {
    $user->saveValuesToDB();
}

它工作正常,但对我来说似乎相当难看,因为User->setEmail() 需要具有混合返回类型,因为电子邮件可能因多种原因无效(未找到域、无效格式、提供空字符串、已使用)由另一个用户等),并且需要将确切原因传回给用户,但据我所知,通常不鼓励混合回报。

我想到的另一种方法是更新所有属性而不执行任何类型的验证,然后在最后执行User->commitAllChanges(),这将执行所有验证,这将从所有setter 方法并将其简化为只有 commit 方法,但我也真的不喜欢这样,因为我觉得实际设置的任何属性都应该是有效的,它仍然没有完全解决问题。

我可以使用哪些其他方法来允许用户设置对象属性并验证它们,将任何错误发送回用户?还是我现在做的很好?

【问题讨论】:

标签: php forms oop validation


【解决方案1】:

我想到的另一种方法是更新所有 属性而不执行任何类型的验证,然后执行 最后的 User->commitAllChanges() 将执行所有 验证,这将从所有 setter 方法并将其简化为仅提交方法

这通常是一个好方法。您还可以将整个过程分解成更小的部分,使其更加通用。

例如,模型可能有一个 validate 方法返回一个布尔值(一切正常,还是有一个或多个错误?),以及一个 getValidationErrors 方法返回一个包含所有信息的数组你关心(可能需要多维)。它可能有一个commit 方法,该方法会自动调用validate,并且只有在完全没有错误的情况下才会保留更改。

但我也真的不喜欢这个,因为我觉得任何属性 实际上被设置应该是有效的,它仍然没有完全得到 解决问题。

“不会被设置”的属性是 IMO 方式比人们最初想象的要麻烦得多。主要问题是,如果要设置属性对象(通过返回一些错误代码,或者更好的是,通过抛出异常),那么你必须用“保护”代码包装对它的所有访问 .这很快就会变得非常乏味。

在实践中,通过辅助方法一次性为所有属性大量赋值会更方便,然后查看结果是什么(例如,通过调用上述validate 方法)。

此外,不要忘记,在实践中,您希望在数据输入字段旁边显示验证错误,并且这些字段被连接以预先填充模型属性的值。因此,一方面属性需要有效,另一方面它们需要与用户输入的内容完全匹配(否则用户会想杀了你)。因此,最好放宽“必须始终有效”的约束以支持实用程序——尽管现在到处都有 AJAX 表单和客户端验证,但这变得不那么相关了。

【讨论】:

  • 我将接受您的回答,因为我同意将所有内容包装在 try/catch 块中是乏味的(在实践中),并且您给出了很好的解释。拥有一个 validate 方法也会允许看似无关的事情返回错误。例如,密码不应该包含用户名,但setPasswordsetUsername 可能被单独调用,并且不一定按正确的顺序调用。此外,一种单一的 setter 方法可能会产生多个错误(例如,密码应大于 8 个字符,并且还需要至少一个数字或一个符号)。
【解决方案2】:

使用try-catch 构造。

// somewhere in your project
class UserValidationException extends \Exception {}

// In your form handler, controller, or whatever you have
try {
    $user->setName($_POST['new_email']);
    $user->setEmail($_POST['new_name']);
    // etc.
}
catch (UserValidationException $e)
{
    // tell user what went wrong using $e->getMessage();
}

// In your User class
class User
{
    // ...
    public function setName($newName)
    {
        if (strlen($newName) < 2)
            throw new UserValidationException('User name is too short');
    }
}

【讨论】:

    【解决方案3】:

    一种方法是保留您的解决方案(setEmail 函数)并使其更加面向对象。 在 setAction 中,您可以设置错误列表

    $this->errors[] = 'Domain invaild';
    

    然后做 1 或 2 个函数从类中获取错误

    public function hasErrors() {return false == empty($this->errors);}
    public function getErrors() {return $this->errors;}
    

    【讨论】:

    • 我接受了 Jon 的回答,因为他给出的解释与您在此处提到的基本相同,但我还是给了您 +1。
    【解决方案4】:

    您当前的方法没有问题,但您可以做的是使用异常。

    它可能看起来像:

    try
    {
        $user->setEmail($_POST['new_email']);
    }
    catch (Exception $e)
    {
        $errors[] = $e->getMessage();
    }
    

    您的 setEmail 方法类似于:

    setEmail($email)
    {
        if ($tooShort) // validate length
            throw new Exception("Email is too short"); // perhaps have custom exceptions
    }
    

    【讨论】:

    • 这也是@MadaraUchiha 在 cmets 中的建议,但用户错误输入电子邮件地址或忘记填写用户名真的是例外情况吗?据我了解,例外情况应仅用于“例外”情况。
    • 我个人会说是的,这是一个例外情况。 setEmail 方法期望电子邮件有效;因此,如果电子邮件无效,则抛出无效电子邮件异常是可以接受的。
    【解决方案5】:

    我对此使用了某种 MVC.NET 方法。基本上抛出异常的坏处是性能,为什么你只想抓住一个?如果电子邮件和密码都无效,请同时让他们知道,而不是'电子邮件不正确' - 修复电子邮件 - 重新发布 - '哦,密码也不正确可能会早点告诉你,但嘿,你只会让我赚钱'

    编辑:
    此外,不应因为验证返回 false 而引发异常,我们希望得到无效数据,无论它是否是故意的(黑客)。但是让我们看看它是如何以大规模形式处理的,看看处理变得多么缓慢。验证错误应该被存储并显示给用户,它应该只导致我们的程序输入条件而不是异常。应该为编码错误保留异常,例如我们需要的资源不存在,有人删除了我们需要正确执行代码的库文件或框架,等等。

    
        <?php
    
        class ChangePasswordModel
        {
    
            protected $OldPassword;
    
            protected $NewPassword;
    
            protected $ConfirmPassword;
    
            protected $ValidationMessages;
    
            protected $DisplayNames;
    
            public function __construct()
            {
                $this->DisplayNames = array(
                    "OldPassword"       => "Old password",
                    "NewPassword"       => "New password",
                    "ConfirmPassword"   => "Confirm new password",
                );
            }
    
            public function LoadPost()
            {
                if ( !isset( $_POST["OldPassword"] ) || !isset( $_POST["NewPassword"] ) || !isset( $_POST["ConfirmPassword"] ) )
                    return;
    
                $this->OldPassword      = trim( $_POST["OldPassword"] );
                $this->NewPassword      = trim( $_POST["NewPassword"] );
                $this->ConfirmPassword  = trim( $_POST["ConfirmPassword"] );
    
                if ( strlen( $this->OldPassword ) < 1 )
                    $this->ValidationMessages["OldPassword"] = "Old password is not set";
                if ( strlen( $this->NewPassword ) < 6 )
                    $this->ValidationMessages["NewPassword"] = "Password must be at least 5 characters.";
                if ( $this->NewPassword != $this->ConfirmPassword )
                    $this->ValidationMessages["ConfirmPassword"] = "Passwords do not match.";
            }
    
            public function ValidationMessageFor( $name )
            {
                if ( !isset( $this->ValidationMessages[$name] ) )
                    return "";
    
                return $this->ValidationMessages[$name];
            }
    
            public function DisplayNameFor( $name )
            {
                // Throw exception if not set
                return $this->DisplayNames[$name];
            }
    
        }
    
        $Model = new ChangePasswordModel();
        $Model->LoadPost();
    
        ?>
        <form action="" method="post">
            <div>
                <?= $Model->DisplayNameFor( "OldPassword" ) ?>
            </div>
            <div>
                <input type="password" name="OldPassword" />
                <span><?= $Model->ValidationMessageFor( "OldPassword" ) ?></span>
            </div>
    
            <div>
                <?= $Model->DisplayNameFor( "NewPassword" ) ?>
            </div>
            <div>
                <input type="password" name="NewPassword" />
                <span><?= $Model->ValidationMessageFor( "NewPassword" ) ?></span>
            </div>
    
            <div>
                <?= $Model->DisplayNameFor( "ConfirmPassword" ) ?>
            </div>
            <div>
                <input type="password" name="ConfirmPassword" />
                <span><?= $Model->ValidationMessageFor( "ConfirmPassword" ) ?></span>
            </div>
    
            <p>
                <input type="submit" value="Change Password" />
            </p>
        </form>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-24
      • 2017-05-19
      • 2016-09-09
      • 2013-09-22
      • 2021-04-13
      • 2014-01-25
      • 2023-04-10
      相关资源
      最近更新 更多