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

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

9

Решение

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

Если User может подтвердить себя, он должен быть в состоянии сделать это изолированно; его валидация не должна касаться каких-либо внешних взаимодействий.

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

Я думаю, что самый простой и надежный способ сделать это — добавить уникальное ограничение на адрес электронной почты в вашей базе данных, а затем в вашем addUser() метод, просто try добавить это. Если ваша база данных сообщает вам, что она не уникальна, то вы знаете, что она не уникальна. Ты не можешь действительно знать заранее.

2

Другие решения

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

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

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

1

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

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

$this->email = $email;
}
}

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

Лучшим способом обеспечения этого было бы использование 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 предоставляет вам интерфейс для сохранения нового пользователя, но эта операция может завершиться неудачей, если не будет соблюдено ограничение уникальности электронной почты.

1

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

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

1

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

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

1

Я думаю, что вы можете использовать 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 и инициализируйте ее в конструкторе. И реализовать все методы для операции добавления / редактирования / удаления в классе модели.

0

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

0
По вопросам рекламы [email protected]