1

我有一个用户可以登录的程序。我有一个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 方法中取出多个返回并减少它仅提交方法,但我真的不喜欢这个,因为我觉得实际设置的任何属性都应该是有效的,它仍然没有完全摆脱问题。

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

4

5 回答 5

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');
    }
}
于 2013-02-27T20:15:06.220 回答
2

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

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

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

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

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

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

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

于 2013-02-27T20:12:40.627 回答
1

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

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

然后制作 1 或 2 个函数以从类中获取错误

public function hasErrors() {return false == empty($this->errors);}
public function getErrors() {return $this->errors;}
于 2013-02-27T20:09:21.310 回答
1

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

它可能看起来像:

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
}
于 2013-02-27T20:12:26.513 回答
0

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

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


    <?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>
于 2013-05-23T20:38:32.707 回答