简体   繁体   English

PHP中的模型验证,需要与数据库进行交互

[英]Model validation in PHP that requires interaction with the database

Let's say I have this model. 假设我有这个模型。 (I made it very simple for demonstration purposes.) (为了演示目的,我非常简单。)

class User
{
    public $id;
    public $email;
    public $password;
    public $errors = [];

    public function isValid()
    {
        if (strpos($this->email, '@') === false) {
            $this->errors['email'] = 'Please enter an email address';
        }
        // ...

        return !$this->errors;
    }
}

And let's say I have this DAO for retrieving, adding, updating, and deleting users. 让我们说我有这个DAO用于检索,添加,更新和删除用户。

class UserDAO
{
    public function getUsers() { ... }

    public function getUserById($id) { ... }

    public function addUser(User $user) { ... }

    public function updateUser(User $user) { ... }

    public function deleteUser($id) { ... }

    public function isEmailUnique($email) { ... }
}

When I process a form, I typically do something like this: 当我处理表单时,我通常会这样做:

$userDAO = new UserDAO();
$user = new User();
$user->email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$user->password = filter_input(INPUT_POST, 'password');

if ($user->isValid()) {
    if ($userDAO->addUser($user)) {
        // ...
    } else {
        // ...
    }
} else {
    // do something with $user->errors
}

Now, let's say part of my user validation should be to check whether email is unique, how do I make it part of the User model? 现在,让我们说我的用户验证的一部分应该是检查电子邮件是否是唯一的,如何使其成为用户模型的一部分? So that, when $user->isValid() is called, it also checks whether the email is unique? 这样,当调用$user->isValid() ,它还会检查电子邮件是否唯一? Or am I doing this all wrong? 或者我这样做是错的?

From my weak understanding of DAOs, DAOs are responsible for all interactions with the database. 由于我对DAO的理解不足,DAO负责与数据库的所有交互。 So how do I make the model work with database from within? 那么如何让模型从内部使用数据库呢?

My recommendation is this: don't consider email address uniqueness when validating your User model. 我的建议如下:在验证User模型时不要考虑电子邮件地址的唯一性。 Uniqueness is a UserDAO problem, not a User problem. 唯一性是UserDAO问题,而不是User问题。

If the User can validate itself, it should be able to do so in isolation; 如果User可以自我验证,它应该能够孤立地进行验证; its validation should not be concerned with any external interactions. 其验证不应涉及任何外部交互。

The only time it matters whether or not the email address is unique is at the instant you attempt to insert it into the database. 电子邮件地址是否唯一的唯一重要时刻是您尝试将其插入数据库的瞬间。 Considering the possibility of multiple concurrent users, it is theoretically possible to validate the uniqueness of the address and have it no longer be unique by the time you attempt to insert it. 考虑到多个并发用户的可能性, 理论上可以验证地址的唯一性,并在您尝试插入地址时使其不再唯一。

I think the most straightforward and dependable way to do this is to add a unique constraint on email address in your database, then in your addUser() method, just try to add it. 我认为最简单可靠的方法是在数据库中为电子邮件地址添加一个唯一约束,然后在addUser()方法中, try添加它。 If your database tells you it is not unique, then you know it is not unique. 如果您的数据库告诉您它不是唯一的,那么您知道它不是唯一的。 You cannot really know beforehand. 你事先不能真正知道。

I think that validation, in this case, is a part of application logic as you require data that isn't stored in the model. 我认为在这种情况下,验证是应用程序逻辑的一部分,因为您需要未存储在模型中的数据。 So it will be better to implement validation logic within a different controller function. 因此,在不同的控制器功能中实现验证逻辑会更好。

Moreover, there is a similar question with the similar answer already: Best Place for Validation in Model/View/Controller Model? 此外,类似的答案已经存在类似的问题: 模型/视图/控制器模型中的最佳验证位置?

Keep the User class as it is, it is a good citizen by itself. 保持User类不变,它本身就是一个好公民。

I would make the method isEmailUnique private (iff it is used just for that) and check by the existence of a User with that e-mail inside addUser . 我会把方法isEmailUnique 私有 (iff它只用于那个)并通过addUser那个电子邮件检查User的存在。 On the other hand, this would pull the responsibility of the logic to the DAO . 另一方面,这会将逻辑的责任拉到DAO (See: Responsibilities and use of Service and DAO Layers ) (参见: 服务和DAO层的责任和使用

So if you change the behavior of isValid to check if the user is already onto the database, you would be breaking your design. 因此,如果您更改isValid的行为以检查用户是否已经进入数据库,那么您将破坏您的设计。

One way to go about this, would be to remove the method User::isValid completely, in favour of passing everything it needs in its constructor, running the validation from there: 解决这个问题的一种方法是完全删除User :: isValid方法,支持在构造函数中传递所需的一切,从那里运行验证:

class User
{
    public function __construct($email) {
        if (strpos($email, '@') === false) {
            throw new \InvalidArgumentException("Invalid email");
        }

        $this->email = $email;
    }
}

If you think about it, what makes a user valid? 如果你考虑一下,是什么让用户有效? If that's a valid email address, make sure you pass one in when you construct the User object. 如果这是一个有效的电子邮件地址,请确保在构造User对象时传入一个。 That makes your User object always valid. 这使您的User对象始终有效。

A better way for ensuring this, would be using a ValueObject which encapsulates this validation logic so you can use it in other objects, avoiding a lot of redundancy and boilerplate code: 确保这一点的更好方法是使用ValueObject来封装此验证逻辑,以便您可以在其他对象中使用它,从而避免大量冗余和样板代码:

class Email
{
    public function __construct($email)
    {
        if (strpos($email, '@') === false) {
            throw new \InvalidArgumentException("Invalid email");
        }

        $this->email = $email;
    }
}

class User
{
    public function __construct(Email $email)
    {
        $this->email = $email;
    }
}

class ProspectiveUser
{
    public function __construct(Email $email)
    {
        $this->email = $email;
    }   
}

Now, in terms of validating a user with the database, you can perfectly encapsulate that within your DAO. 现在,在使用数据库验证用户方面,您可以在DAO中完美地封装它。 The DAO can perform the check of ensuring that the user is not in the database already, keeping the DAO consumer agnostic of it, except from the fact that it should know how to handle the case of an error when the user already exists in the DB: DAO可以执行检查以确保用户不在数据库中,保持DAO消费者不知道它,除非它应该知道当用户已经存在于DB中时如何处理错误的情况:

class UserDAO
{
    public function recordNewUser(User $user)
    {
        if ($this->userExists()) {
            throw new UserAlreadyExistsException();
        }

        $this->persist($user);
        $this->flush($user);
    }

    private function userExists(User $user)
    {
        $user = $this->findBy(['email' => $user->getEmail()]);

        return !is_null($user);
    }
}

As you can see, the DAO provides you with an interface for saving a new user, but that operation might fail if the constraint of email uniqueness is not satisfied. 如您所见,DAO为您提供了用于保存新用户的界面,但如果不满足电子邮件唯一性的约束,则该操作可能会失败。

I would take all the validation issues out of User class and move to Controller layer (which can eg call UserDAO to check email uniqueness). 我会从User类中获取所有验证问题并转移到Controller层(例如,可以调用UserDAO来检查电子邮件的唯一性)。 It is best to keep User class simply as an Entity class and put all the other stuff in other classes - otherwise it will grow and grow to the state that is not maintainable anymore :) 最好将User类简单地保存为Entity类,并将所有其他内容放在其他类中 - 否则它将增长并增长到不再可维护的状态:)

Check also: https://en.wikipedia.org/wiki/Single_responsibility_principle 请参阅https//en.wikipedia.org/wiki/Single_responsibility_principle

The UserDAO class must implement a method called userExists . UserDAO类必须实现一个名为userExists的方法。 This method only checks if the email address already exists. 此方法仅检查电子邮件地址是否已存在。 It checks this in the BD so its place is in the UserDAO class. 它在BD中检查它,因此它的位置在UserDAO类中。 It must be a private method and addUser uses it to return a correct value or false/null 它必须是私有方法,addUser使用它来返回正确的值或false / null

I think you can use DAO as argument for validation function. 我认为您可以使用DAO作为验证功能的参数。

public function isValid($dao)
{
    if (strpos($this->email, '@') === false) {
        $this->errors['email'] = 'Please enter an email address';
    }
    if ($dao->isEmailUnique($this->email) === false) {
        $this->errors['email'] = 'Email address should be unique';
    }
    // ...

    return !$this->errors;
}

But may be better way is use DAO inside your User model. 但可能更好的方法是在用户模型中使用DAO。 Add to model private variable $dao and init it in constructor. 添加到模型私有变量$ dao并在构造函数中初始化它。 And implement all methods for add/edit/delete operation in model class. 并实现模型类中添加/编辑/删除操作的所有方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM