Melodic писал(а):Правильным ли будет сделать через возможности Yii (не хотелось бы отказываться от ActiveForm)
Показывать красивые ошибки - дело UI и форм. Дело доменной модели - кидать исключения.
К валидации есть, по крайней мере, три подхода:
В первом мы предполагаем, что данные приходят в сервис и модель "нефильтрованными". Тогда в сервисе и сущностях везде пичкаем валидацию всего и вся. Сервисы и сущности при этом разбухают, выразительнось теряется за тоннами проверок. Получается "Любые данные -> Сервис с проверками -> Модель с проверками". Можно вынести валидации в отдельное место... но это уже третий вариант.
При втором предполагаем, что все данные, пришедшие в сервис или сущность, уже провалидированы формами фреймворка. Тогда в сервисах оставляем только важные логические проверки (например, на уникальность), а на остальное забиваем. Пользуемся формами и валидаторами фреймворка. Получается "Форма с валидацией -> Сервис -> Модель". Сервисы и модели чистые, но приходится реализовывать аналогичную фреймворковскую валидацию для API или консоли.
В третьем имеем ситуацию, когда у нас всё поступает в сервис через DTO (вроде Command). Тогда, как и предложили, валидировать можно целиком сам DTO. Получается "Любые данные -> DTO -> Валидатор DTO -> Сервис -> Модель". С CommandBus это решается элементарно через
создание валидаторов для команд по аналогии с хендлерами. Валидация внедрена на уровне шины. Формы, консоль и API при этом можно даже вообще не валидировать, предавая хоть напрямую поля из $_POST.
В проектах без API склоняюсь к среднезатратному варанту: Для слоя UI пользуюсь контроллерами, представлениями и формами фреймворка (с их валидацией и прочими плюшками). А в модели делаю только логические проверки.
Оставляю Yii-шную форму, но эта форма используется только для принятия данных и базового "красивого" предварительного вывода ошибок валидации. Можно, при желании, внедрить проверки на уникальность. Но никакого метода signup в ней нет:
Код: Выделить всё
class SignupForm extends Model
{
public $username;
public $email;
public $password;
public $verifyCode;
private $userRepository;
public function __construct(UserRepositoryInterface $userRepository, $config = [])
{
$this->userRepository = $userRepository;
parent::__construct($config);
}
public function rules()
{
return [
[['username', 'email', 'password'], 'filter', 'filter' => 'trim'],
[['username', 'email', 'password'], 'required'],
['username', 'match', 'pattern' => '#^[\w_-]+$#i'],
['username', 'string', 'min' => 2, 'max' => 255],
['username', 'validateUsername'],
['email', 'email'],
['email', 'validateEmail'],
['password', 'string', 'min' => 6],
['verifyCode', 'captcha', 'captchaAction' => '/site/captcha'],
];
}
public function validateUsername($attribute)
{
if ($this->userRepository->existsByUsername($this->$attribute)) {
$this->addError($attribute, 'This username has already been taken.');
}
}
public function validateEmail($attribute)
{
if ($this->userRepository->existsByEmail($this->$attribute)) {
$this->addError($attribute, 'This email has already been taken.');
}
}
}
Дальше валидные данные из этой формы уже кидаются в сервис:
Код: Выделить всё
public function actionSignup()
{
$form = Yii::createObject(SignupForm::className());
if ($form->load(Yii::$app->request->post()) && $form->validate()) {
$this->userService->requestSignup(
$form->username,
$form->email,
$form->password
);
Yii::$app->session->setFlash('success', 'Please confirm your Email.');
return $this->goHome();
}
return $this->render('signup', [
'model' => $form,
]);
}
public function actionConfirmSignup($token)
{
$this->userService->confirmSignup($token);
Yii::$app->getSession()->setFlash('success', 'Email is confirmed successfully.');
return $this->goHome();
}
А в сервисе оставляем только критичные проверки:
Код: Выделить всё
class UserService
{
...
public function requestSignup($username, $email, $password)
{
$this->guardUsernameIsUnique($username);
$this->guardEmailIsUnique($email);
$user = User::requestSignup(
$username,
$email,
$password,
$this->passwordHasher,
$this->authTokenizer,
);
$this->userRepository->add($user);
}
public function confirmSignup($token)
{
$user = $this->userRepository->findByEmailConfirmToken($token);
$user->confirmSignup();
$this->userRepository->save($user);
}
...
private function guardUsernameIsUnique($username, $exceptId = null)
{
if ($this->userRepository->existsByUsername($username, $exceptId)) {
throw new \DomainException('Username already exists');
}
}
private function guardEmailIsUnique($email, $exceptId = null)
{
if ($this->userRepository->existsByEmail($email, $exceptId)) {
throw new \DomainException('Email already exists');
}
}
}
В этом примере не заморачиваюсь с DTO, а передаю просто аргументами.
В итоге спокойно используем ActiveForm, валидации фреймворка и прочие вещи.