@daMage

Как и где правильно осуществлять валидацию данных в MVC?

Уже который день ломаю голову над тем, как и где лучше проводить валидацию данных. В документации к различным фреймворкам, я вижу что-то вроде $form->isValid() и ни слова о том, что происходит далее. На ум приходит несколько способов, но прежде нужно описать задачу, пускай это будет регистрация пользователя. Есть интерфейс

UserController::public function postRegisterUser(Request $request);

UserModel::public function register($name, $email, $password);

Напишите какая реализация лучше и почему:

1.
public function postRegisterUser(Request $request, UserModel $user) {
	$user->find($request->get('id'))->register(
		$request->get('name'),
		$request->get('email'),
		$request->get('password')
	);
}

public function register($name, $email, $password) {
	$this->validator()
		->add(new String($name, 'name'))
		->add(new Email($email, 'email'))
		->add(new Password($password, 'password'))
		->validate();

	// register new user
}


2.
public function postRegisterUser(RegisterUserRequest $request, UserModel $user) {
	$user->find($request->get('id'))->register(
		$request->get('name'),
		$request->get('email'),
		$request->get('password')
	);
}

public function register($name, $email, $password) {
	// register new user
}


3.
public function postRegisterUser(RegisterUserRequest $request, UserModel $user) {
	$user->find($request->get('id'))->register($request);
}

public function register(RegisterUserRequest $request) {
	// register new user
}


На данный момент пользуюсь первым способом, но у него есть пару недостатков. Во-первых, очень часто данные пересекаются - при регистрации и изменении данных пользователя будут использоваться одни и те же поля, и тут либо дублировать проверку, либо выносить в отдельный метод. Хуже всего, когда приходится писать что-то вроде

public function register($name, $email, $password) {
	$this->validateBaseData($name, $email, $password)
		->add(new String($foo, 'foo'))
		->validate();

	// register new user
}


Не сказал бы, что это критично, но, как по мне, ухудшается читаемость. Во-вторых, есть более приятный для меня стиль описания фильтров.

Второй способ подразумевает, что я создаю отдельный класс для каждого POST запроса.

class RegisterUserRequest extends Request {
	/**
	 * @return \Core\Validation\Param\String
	 */
	public function name() {
		return new String;
	}

	/**
	 * @return \Core\Validation\Param\Email
	 */
	public function email() {
		return new Email;
	}

	/**
	 * @return \Core\Validation\Param\Password
	 */
	public function password() {
		return new Password;
	}
}


Такой вариант мне нравится больше всего в плане читаемости, к тому же присутствует наследование. Но есть серьезный минус - валидация происходит при иньекции в метод контроллера, и модель не уверена, что данные пришли верные. Впринципе, любое изменение данных идет через веб-морду и, так или иначе, данные проходят через контроллер, но чувство волнения не покидает.

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

Разумеется, приветствуются и другие варианты.
  • Вопрос задан
  • 1633 просмотра
Решения вопроса 2
27cm
@27cm
TODO: Написать статус
UserModel::public function register($name, $email, $password);

Регистрирование нового пользователя это ну никак не метод UserModel. Методы моделей - это действия, производимые с объектом модели. Если бы он был статическим, это ещё куда ни шло.

Вообще странно у вас всё устроено. Вроде бы пользователь только регистрируется, т. е. объект UserModel должен создаваться по окончанию процедуры регистрации, а у вас $user приходит на вход postRegisterUser(..., UserModel $user). А ещё потом это $user->find($request->get('id')). Что ещё за id? Если пользователя только собираетесь регистрировать, у него ещё не должно быть id, или я что-то не понимаю? Ну это так, к слову...


В документации к различным фреймворкам, я вижу что-то вроде $form->isValid() и ни слова о том, что происходит далее.

Чтобы понять, что происходит дальше, нужно понять, что же собой представляет $form. Это класс формы, хранящий внутри себя список названий полей ввода (в вашем случае 'name', 'email', 'password'), а также все необходимые фильтры и валидаторы для этих полей. Форма, строго говоря, ничего не знает ни о контроллерах, ни даже о UserModel, её задача - принять, отфильтровать, проверить данные, вернуть список ошибок, если данные не корректны. Для каждой html формы создаётся и конфигурируется свой класс формы. В итоге получаем что-то вроде:

public function postRegisterUser(Request $request)
{
    $form = $this->getRegisterForm();
    // Передаём данные $_POST. Ключи не перечисляем - форма сама разберётся, что взять
    $form->setData($request->getPost());
    if ($form->isValid()) {
        // Получаем уже отфильтрованные данные
        $data = $form->getData();

        // Регистрируем нового пользователя
        $user = UserModel::register($data['name'], $data['email'], $data['password']);
    }
}

static public function register($name, $email, $password) 
{
    // Регистрируем нового пользователя
}
Ответ написан
@rsi
software engineer
Все зависит от архитектуры приложения. В идеале каждый класс должен работать только с безопасными данными, то есть прошедшими все необходимые проверки.

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

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

Войдите, чтобы написать ответ

Похожие вопросы