Проверка модели в PHP, которая требует взаимодействия с базой данных

предположим, у меня есть эта модель. (Я сделал это очень просто для демонстрационных целей.)

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;
    }
}

и предположим, у меня есть этот 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) { ... }
}

когда я обрабатываю форму, я обычно делаю что-то вроде этого:

$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
}

теперь, скажем, часть моей проверки пользователя должна быть, чтобы проверить, является ли электронная почта уникальной, как сделать ее частью модели пользователя? Так, что когда $user->isValid() вызывается, он также проверяет является ли письмо уникальным? Или я все делаю неправильно?

из моего слабого понимания DAOs, DAOs отвечают за все взаимодействия с базой данных. Итак, как заставить модель работать с базой данных изнутри?

7 ответов


моя рекомендация такова: не учитывайте уникальность адреса электронной почты при проверке вашего User модель. Уникальность


сохранить User класс, как он есть, сам по себе является хорошим гражданином.

Я бы сделал метод isEmailUnique частная (если он используется именно для этого) и проверить на наличие User с этим письмом внутри addUser. С другой стороны, это приведет к тому, что ответственность за логику ляжет на DAO. (см.: ответственность и использование уровней сервиса и DAO)

так если вы измените поведение isValid чтобы проверить, если пользователь уже находится в базе данных, вы будете нарушать свой дизайн.


один из способов сделать это, было бы удалить метод User:: isValid полностью, в пользу передачи всего, что ему нужно в его конструкторе, запустив проверку оттуда:

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

        $this->email = $email;
    }
}

Если вы думаете об этом, что делает пользователь действует? Если это действительный адрес электронной почты, убедитесь, что вы передаете его при создании объекта пользователя. Это делает ваш объект пользователя всегда действительным.

лучшим способом обеспечения этого было бы использование 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;
    }   
}

Теперь, с точки зрения проверки пользователя с базой данных, вы можете прекрасно инкапсулировать это в своем DAO. DAO может выполнить проверку того, что пользователь уже не находится в базе данных, сохраняя агностику потребителя DAO, за исключением того, что он должен знать, как обрабатывать случай ошибки, когда пользователь уже существует в базе данных ДБ:

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);
    }
}

как вы можете видеть, DAO предоставляет вам интерфейс для сохранения нового пользователя, но эта операция может завершиться неудачей, если ограничение уникальности электронной почты не удовлетворено.


Я думаю, что проверка в этом случае является частью логики приложения, поскольку вам нужны данные, которые не хранятся в модели. Поэтому будет лучше реализовать логику проверки в рамках другой функции контроллера.

более того, есть аналогичный вопрос с аналогичным ответом уже:лучшее место для проверки в модели/модели/контроллера?


Я бы взял все проблемы проверки от User класс и переход на уровень контроллера (который может, например, вызвать UserDAO для проверки уникальности электронной почты). Лучше держать User class просто как класс сущности и поместить все остальные вещи в другие классы-в противном случае он будет расти и расти до состояния, которое больше не поддерживается :)

проверьте также:https://en.wikipedia.org/wiki/Single_responsibility_principle


Я думаю, вы можете использовать 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;
}

но может быть лучше использовать DAO внутри вашей модели пользователя. Добавьте в модель закрытую переменную $dao и введите ее в конструктор. И реализовать все методы для операции добавления / редактирования / удаления в классе модели.


класс UserDAO должен реализовать метод с именем userExists. Этот метод проверяет только если адрес электронной почты уже существует. Он проверяет это в BD, поэтому его место находится в классе UserDAO. Это должен быть частный метод, и addUser использует его для возврата правильного значения или false / null