Посоветуйте как организовать структуру

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
psevdo
Сообщения: 96
Зарегистрирован: 2013.04.10, 11:10

Посоветуйте как организовать структуру

Сообщение psevdo »

Смотрел видео с конференций по Yii. Выступающие говорили, что модель основанная на ActiveRecord это не совсем та модель, которая относится к MVC. Сказал как то не очень понятно, но надеюсь смысл понятен. Также предлагалось всю логику выводить в отдельный класс, который и будет модель в MVC. Мне это показалось правильным и удобным. Я теперь хочу опробовать это написан модуль регистрации/авторизации. Начнем с регистрации...
Вот структура модуля:
user
--- Module.php
--- controllers
--- --- SignupController.php
--- common
--- --- SignUp.php // та самая модель MVC
--- models
--- --- User.php // модель Yii \yii\db\ActiveRecord
--- --- SignUpForm.php // модель Yii \yii\base\Model
--- views
--- --- signUp.php // форма регистрации

Т.е. мы имеем модель SignUpForm для вывода формы. А модель User служит для добавления/регистрации нового пользователя. А вот классе в commom\SignUp как раз и будет вся логика регистрации. Из этого класса будет вызываться User::save(). Вопрос: где валидировать нового пользователя перед добавлением его в базу (в каком из этих двух классов)? И может наличие SignUpForm излишне и проще использовать сразу User?
Подскажите пожалуйста.
Аватара пользователя
SiZE
Сообщения: 2817
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Посоветуйте как организовать структуру

Сообщение SiZE »

По простому без мапперов и репозиториев:

1. Контроллер передает данные в форму (SignUpForm)
2. Форма валидирует данные
3. Если данные валидны контроллер: или передает объект формы в сервис (SignUp) или из формы в сервис
4. Сервис заполняет AR (User) данными из формы.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Посоветуйте как организовать структуру

Сообщение zelenin »

модель MVC - это не конкретная модель/класс, а просто слой приложения, работающий с данными. В том числе это и модель AR и модель формы.
Исхордя из этого, не понятно что вы сделали тут - SignUp.php // та самая модель MVC
psevdo
Сообщения: 96
Зарегистрирован: 2013.04.10, 11:10

Re: Посоветуйте как организовать структуру

Сообщение psevdo »

Хорошо. Наверное перемудрил. Логика будет в файле SignUpForm.php.
Это метод контроллера:

Код: Выделить всё

public function actionIndex() {

		$model = new SignUpForm();

		if(\Yii::$app->request->post('SignUpForm')) {
			$model->attributes = \Yii::$app->request->post('SignUpForm');
			$model->signUp();
		}

		return $this->render('signUp', [
			'model' => $model
		]);
}
Это модель:

Код: Выделить всё

class SignUpForm extends \yii\base\Model {

	public $username;
	public $password;

	public function rules() {
		return [
		   ---------------
		];
	}

	public function signUp() {

		if($this->validate()) {
			$user = new User();
			$user->attributes = $this->attributes;
			$user->save();
		} else {
			echo 'n';
		}
	}
}

Код: Выделить всё

/
class User extends \yii\db\ActiveRecord
{

    public static function tableName()
    {
        return '{{%user}}';
    }


    public function rules()
    {
    	return [];
    }
}
Вот на сколько это правильно? Я хотел разгрузить ActiveRecord User. Как при таком подходе правильно организовать валидацию? Ведь что бы выполнилось $user->save() нужны правила валидации в User.php. Также нужны правила валидации в SignUpForm.php, что бы ActiveForm сообщал об ошибках ввода.
Аватара пользователя
SiZE
Сообщения: 2817
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Посоветуйте как организовать структуру

Сообщение SiZE »

Код: Выделить всё

public function actionIndex()
{
    $model = new SignUpForm();
    if ($model->load()) {
        try {
            $service = Yii::createObject(SignUpService::class);
            $service->signUpFromSignUpForm($model);
        } catch (/* тут перехват logic, domain и runtime исключений*/) {
            /* обработка исключения */
        }
    }

    return $this->render('signUp', [
        'model' => $model
    ]);
}


class SignUpService
{
    private $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function signUpFromSignUpForm(SignUpForm $model)
    {
        if (!$model->validate()) {
            throw new RuntimeException('Model is invalid');
        }

        return $this->create($model->username, $model->password);
    }

    public function create($username, $password)
    {
        $user = clone $this->user;
        $user->username = $username;
        $user->password = $password;
        if (!$user->save(false)) {
             throw new RuntimeException('Can not save model');
        }

        return $user;
    }
}
Последний раз редактировалось SiZE 2017.09.06, 15:44, всего редактировалось 1 раз.
psevdo
Сообщения: 96
Зарегистрирован: 2013.04.10, 11:10

Re: Посоветуйте как организовать структуру

Сообщение psevdo »

А метод SignUpService::create() для чего? Где он должен вызываться? И для чего объект клонируется?
И все таки я не пойму... В каком классе должны быть правила валидации, что бы не было дублирования? Или без этого не обойтись?
Аватара пользователя
SiZE
Сообщения: 2817
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Посоветуйте как организовать структуру

Сообщение SiZE »

psevdo писал(а): 2017.09.06, 12:00 А метод SignUpService::create() для чего? Где он должен вызываться?
Сорян, подправил пример.
psevdo писал(а): 2017.09.06, 12:00 И для чего объект клонируется?
Это для примера, можешь без DI обойтись и клонирования. Но таким образом у тебя всегда будет первоначальный объект внедренный через DI, если ты повторно вызовешь create ты будешь уверен в состоянии этого объекта.
psevdo писал(а): 2017.09.06, 12:00 И все таки я не пойму... В каком классе должны быть правила валидации, что бы не было дублирования? Или без этого не обойтись?
Я лично не вижу смысла отказываться от штатной валидации. Но без проверки в сервисе тоже не обойтись. В контроллере перехватываем исключения из сервиса и в зависимости от исключения: преобразуем его например в HttpException или же $model->addError('attribute', 'error'); или пишем лог и игнорируем. Думать надо вобщем.
Аватара пользователя
SiZE
Сообщения: 2817
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Посоветуйте как организовать структуру

Сообщение SiZE »

Код: Выделить всё

try {
    $service = Yii::createObject(SignUpService::class);
    $service->signUpFromSignUpForm($model);
} catch (RuntimeException $e) {
    // Пример где-то тут был от Elisdn, типа такого
    throw new HttpException(403, Yii::t('signupservice', $e->getMessage())); // переводим сообщения в человекопонятные :)
}
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Посоветуйте как организовать структуру

Сообщение ElisDN »

SiZE писал(а): 2017.09.06, 15:54 Пример где-то тут был от Elisdn

Код: Выделить всё

class SignupController extends Controller
{
    private $service;

    public function __construct($id, $module, SignupService $service, $config = [])
    {
        parent::__construct($id, $module, $config);
        $this->service = $service;
    }

    public function actionRequest()
    {
        $form = new SignupForm();
        if ($form->load(Yii::$app->request->post()) && $form->validate()) {
            try {
                $this->service->signup($form);
                Yii::$app->session->setFlash('success', 'Check your email for further instructions.');
                return $this->goHome();
            } catch (\DomainException $e) {
                Yii::$app->errorHandler->logException($e);
                Yii::$app->session->setFlash('error', Yii::t('exception', $e->getMessage()));
            }
        }
        return $this->render('request', [
            'model' => $form,
        ]);
    }

    public function actionConfirm($token)
    {
        try {
            $this->service->confirm($token);
            Yii::$app->session->setFlash('success', 'Your email is confirmed.');
            return $this->redirect(['auth/login']);
        } catch (\DomainException $e) {
            Yii::$app->errorHandler->logException($e);
            Yii::$app->session->setFlash('error', Yii::t('exception', $e->getMessage()));
        }
        return $this->goHome();
    }
}
psevdo
Сообщения: 96
Зарегистрирован: 2013.04.10, 11:10

Re: Посоветуйте как организовать структуру

Сообщение psevdo »

А вообще, что бы вывести форму какую модель Yii лучше использовать \yii\db\ActiveRecord или \yii\base\Model? Если можно, то аргументировано
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Посоветуйте как организовать структуру

Сообщение zelenin »

ActiveRecord - это модель, сохраняющаяся в базу, Model - это просто модель, т.е. класс хранящий данные. Форма - это данные. Ответ: Model.
psevdo
Сообщения: 96
Зарегистрирован: 2013.04.10, 11:10

Re: Посоветуйте как организовать структуру

Сообщение psevdo »

ElisDN писал(а): 2017.09.06, 16:26

Код: Выделить всё

class SignupController extends Controller
{
    private $service;

    public function __construct($id, $module, SignupService $service, $config = [])
    {
        parent::__construct($id, $module, $config);
        $this->service = $service;
    }
}
А как передать параметры в конструктор контроллера? А зачем это нужно?
Моя IDE сама создала такой конструктор:

Код: Выделить всё

public function __construct($id, Module $module, array $config = []) {
		parent::__construct($id, $module, $config);
	}
Но в браузере я получаю:
Class app\modules\user\controllers\Module does not exist
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Посоветуйте как организовать структуру

Сообщение ElisDN »

psevdo писал(а): 2017.09.08, 07:36 А как передать параметры в конструктор контроллера? А зачем это нужно?
Их сам фреймворк передаёт. Затем, чтобы не вызывать вручную $service = new SignupService() в каждом экшене.
psevdo писал(а): 2017.09.08, 07:36 Моя IDE сама создала такой конструктор, но в браузере я получаю Class app\modules\user\controllers\Module does not exist
Либо уберите слово Module, либо импортируйте класс как use yii\base\Module.
Аватара пользователя
Йож
Сообщения: 574
Зарегистрирован: 2015.08.26, 03:05

Re: Посоветуйте как организовать структуру

Сообщение Йож »

А конкретные описания ошибок "catch (\DomainException $e) " в сервисе находятся?
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Посоветуйте как организовать структуру

Сообщение ElisDN »

Код: Выделить всё

class User extends ActiveRecord
{
    ...

    public function confirmSignup(): void
    {
        if (!$this->isWait()) {
            throw new \DomainException('User is already active.');
        }
        $this->status = self::STATUS_ACTIVE;
        $this->email_confirm_token = null;
        $this->recordEvent(new UserSignUpConfirmed($this));
    }
}
http://www.elisdn.ru/blog/105/services-and-controllers
Ответить