Form, Model, AR

Обсуждаем, как правильно строить приложения
Auramel
Сообщения: 80
Зарегистрирован: 2017.11.17, 14:39
Откуда: Russia, Ufa
Контактная информация:

Form, Model, AR

Сообщение Auramel »

Привет. Есть несколько вопросов....

Часто можно встретить что-то подобное

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

class LoginForm extends \yii\base\Model
{
	# properties
	# rules
	# labels
	
	public function login()
	{
		\Yii::$app->user->login($user);
	}
}
Помню, что видел гневные отзывы на это дело. То бишь часто делают авторизацию\регистрацию и прочие подобные штуки в форме. Правильнее будет, если их вынести в ответственный класс?

В некоторых проектах для вывода данных из бд не используется GridView. Контроллер рендерит файл и передает туда массив. Что-то типа такого:

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

public function actionAll(): string
{
	return $this->render('all', [
		'users' => UserService::getAllUsers()
	]); 
}
Во view, часто нужны какие-нибудь "крутые" штучки. Допустим, создаем собственный грид и хотим вывести attributeLabels. Для этого создавал виджет - UserWidget (как пример) и добавлял туда что-то типа такого:

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

public static function getForm(): UserForm
{
	return UserForm::instance();
}

public static function getAttributeLabel(string $label): string
{
	return self::getForm()->getAttributeLabel($label);
}
На yii conf 2017 Дмитрий Елисеев что-то подобное как раз и рассказывал. Там он предлагал ViewModel делать. Я плохо понял этот момент. Из того же доклада: Дмитрий предложил в формы добавлять методы типа getUsers() который вернет, допустим, пользователей в виде массива для dropdownlist. Насколько это целесообразно? Для подобных целей использую виджеты...

И, чисто ради интереса, спрошу про ActiveRecord.

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

User::findUserByLogin('admin')
или

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

User::find()->withLogin('admin')
что будет более каноном?
andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Form, Model, AR

Сообщение andku83 »

Auramel писал(а): 2018.03.22, 12:57 И, чисто ради интереса, спрошу про ActiveRecord.

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

User::findUserByLogin('admin')
или

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

User::find()->withLogin('admin')
что будет более каноном?
первый вариант возвращает объект выполняя запрос внутри, второй это фильтр возвращающий Query и позволяющий добавить еще что-нибудь
Auramel
Сообщения: 80
Зарегистрирован: 2017.11.17, 14:39
Откуда: Russia, Ufa
Контактная информация:

Re: Form, Model, AR

Сообщение Auramel »

andku83 писал(а): 2018.03.22, 13:22
Auramel писал(а): 2018.03.22, 12:57 И, чисто ради интереса, спрошу про ActiveRecord.

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

User::findUserByLogin('admin')
или

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

User::find()->withLogin('admin')
что будет более каноном?
первый вариант возвращает объект выполняя запрос внутри, второй это фильтр возвращающий Query и позволяющий добавить еще что-нибудь
Чё-то аж сам офигел, что подобное спросил xD логично же все
Nex-Otaku
Сообщения: 831
Зарегистрирован: 2016.07.09, 21:07

Re: Form, Model, AR

Сообщение Nex-Otaku »

Auramel писал(а): 2018.03.22, 12:57 часто делают авторизацию\регистрацию и прочие подобные штуки в форме. Правильнее будет, если их вынести в ответственный класс?
Да.

См. SRP (Принцип единой ответственности).

Но в форму удобно бывает запихнуть то, что относится только к самой форме. Например, у меня в форме валидация и методы загрузки и выгрузки данных.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Form, Model, AR

Сообщение anton_z »

Auramel писал(а): 2018.03.22, 12:57
Помню, что видел гневные отзывы на это дело. То бишь часто делают авторизацию\регистрацию и прочие подобные штуки в форме. Правильнее будет, если их вынести в ответственный класс?
Люди, которые так пишут, знают распиаренный SOLID но еще пока не знают/не понимают GRASP. В GRASP есть шаблон InfirmationExpert. Если кратко, "Какой объект должен выполнять эту функцию? Тот у которого есть для этого все данные (или ключевые данные)." У LoginForm есть все данные для входа, поэтому туда и добавляют метод login(). Если логин сложный и должен быть реюзабелен, форма делегирует все операции по логину другому объекту, передавая ему все данные. которые посчитает нужным(!) В вашем примере так и написано:

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

public function login() {
    \Yii::$app->user->login($user);
}
Я считаю этот пример корректным в плане распределения обязанностей.

Предлагаемая альтернатива - клиентский код пихает данные в форму, валидирует ее, вытаскивает данные из формы, передает их другому объекту для логина. Этот такой классический процедурный data flow. Многим людям он действительно кажется проще, так как процедурная парадигма воспринимаеттся как естественная форма декомпозиции (чтобы решить задачу, надо сделать 1, сделать 2, сделать 3 и т. д.).
Auramel писал(а): 2018.03.22, 12:57
Дмитрий предложил в формы добавлять методы типа getUsers() который вернет, допустим, пользователей в виде массива для dropdownlist. Насколько это целесообразно? Для подобных целей использую виджеты...
Здесь согласен, сам так делаю.
Auramel писал(а): 2018.03.22, 12:57
И, чисто ради интереса, спрошу про ActiveRecord.

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

User::findUserByLogin('admin')
или

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

User::find()->withLogin('admin')
что будет более каноном?
Конечно, второй вариант лучше. Так как там метод будет нестатический и будет находиться в query классе, который специально предназначен для получения экземпляров AR.

Если хочется краткости:

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


User::find()->findByLogin('admin');

Внутри findByLogin будет это:

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


public function findByLogin($login) {
	return $this->withLogin($login)->one();
}


Избегайте статических методов в AR для таких целей. И вообще если есть вариант сделать нестатический метод, его лучше всегда предпочесть статическому. Статика плохо мокается (синлтоны - исключение).
Последний раз редактировалось anton_z 2018.03.23, 08:59, всего редактировалось 2 раза.
Auramel
Сообщения: 80
Зарегистрирован: 2017.11.17, 14:39
Откуда: Russia, Ufa
Контактная информация:

Re: Form, Model, AR

Сообщение Auramel »

Nex-Otaku писал(а): 2018.03.22, 21:17
Auramel писал(а): 2018.03.22, 12:57 часто делают авторизацию\регистрацию и прочие подобные штуки в форме. Правильнее будет, если их вынести в ответственный класс?
Да.

См. SRP (Принцип единой ответственности).

Но в форму удобно бывает запихнуть то, что относится только к самой форме. Например, у меня в форме валидация и методы загрузки и выгрузки данных.
В своих проектах авторизацию выношу в сервис. На уровне формы делаю валидацию только типа required, string, mim/max. Вещи типа validatepass или unique делаю также на уровне сервисов.

Имхо, форма (та, где мы пишем, допустим, логин т пароль) должна лишь проверить целостность данных. Тогда куда деть валидацию пасса и поиск по логину? Все больше склоняюсь, что для этого нужно создать вторую форму, которая нигде не отображается. В нее грузим данные с формы амортизации. Делаем validste(). Потом, getUser() и в контроллере/сервисе/фасаде(?) Делаем login. Коллеги, что думаете на этот счёт?
Auramel
Сообщения: 80
Зарегистрирован: 2017.11.17, 14:39
Откуда: Russia, Ufa
Контактная информация:

Re: Form, Model, AR

Сообщение Auramel »

С viewmodel стало более-менее понятно. Люди пишут, что это типа dto из ддд(?). В инете пару примеров нашел, разберусь.

Насчёт AR. Тоже не люблю статику :) и использование ->withLogin() как-то круче смотрится (имхо). Наверное, все-таки, надо попробовать с этим делом поработать и протестировать, мб, чё само вплывет...

А вот использование в форме getUsers() кажется мне неправильным и правильным одновременно. Пока склоняюсь, что да, так и должно быть. Тоже надо поработать с этим...

И да, я не знаю grasp. Sam dark в топике ниже мне дал на него ссылку (указатель). Да вот я ещё с простыми паттернами не разобрался...
Всем спасибо )
Nex-Otaku
Сообщения: 831
Зарегистрирован: 2016.07.09, 21:07

Re: Form, Model, AR

Сообщение Nex-Otaku »

Auramel писал(а): 2018.03.23, 06:55 куда деть валидацию пасса и поиск по логину?
Вынести в отдельные классы валидаторов. В форме указать их.
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Form, Model, AR

Сообщение SiZE »

Auramel писал(а): 2018.03.23, 07:04 С viewmodel стало более-менее понятно. Люди пишут, что это типа dto из ддд(?).
DTO - data transfer object. Это просто объект с набором атрибутов. Viewmodel децл другое.
Последний раз редактировалось SiZE 2018.03.23, 09:21, всего редактировалось 1 раз.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Form, Model, AR

Сообщение ElisDN »

DTO - data transfer object.
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Form, Model, AR

Сообщение SiZE »

ElisDN писал(а): 2018.03.23, 09:14 DTO - data transfer object.
Да. Точно )
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: Form, Model, AR

Сообщение noLogicOnlyWar »

anton_z писал(а): 2018.03.23, 02:27
Люди, которые так пишут, знают распиаренный SOLID но еще пока не знают/не понимают GRASP. В GRASP есть шаблон InfirmationExpert. Если кратко, "Какой объект должен выполнять эту функцию? Тот у которого есть для этого все данные (или ключевые данные)." У LoginForm есть все данные для входа, поэтому туда и добавляют метод login(). Если логин сложный и должен быть реюзабелен, форма делегирует все операции по логину другому объекту, передавая ему все данные. которые посчитает нужным(!) В вашем примере так и написано:

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

public function login() {
    \Yii::$app->user->login($user);
}
Так буквально все понимать - так у вас год объект на год объекте будет.
Откуда у вас в методе формы $user появляется?
Auramel
Сообщения: 80
Зарегистрирован: 2017.11.17, 14:39
Откуда: Russia, Ufa
Контактная информация:

Re: Form, Model, AR

Сообщение Auramel »

noLogicOnlyWar писал(а): 2018.03.23, 15:31
anton_z писал(а): 2018.03.23, 02:27
Люди, которые так пишут, знают распиаренный SOLID но еще пока не знают/не понимают GRASP. В GRASP есть шаблон InfirmationExpert. Если кратко, "Какой объект должен выполнять эту функцию? Тот у которого есть для этого все данные (или ключевые данные)." У LoginForm есть все данные для входа, поэтому туда и добавляют метод login(). Если логин сложный и должен быть реюзабелен, форма делегирует все операции по логину другому объекту, передавая ему все данные. которые посчитает нужным(!) В вашем примере так и написано:

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

public function login() {
    \Yii::$app->user->login($user);
}
Так буквально все понимать - так у вас год объект на год объекте будет.
Откуда у вас в методе формы $user появляется?
чисто как пример. В рабочем варанте было бы что-то типа $this->_user или $this->getUser(); Короче, IdentityInterface.
Рад, что люди знают, как расшифровывается аббревиатура DTO. Спасибо.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Form, Model, AR

Сообщение anton_z »

noLogicOnlyWar писал(а): 2018.03.23, 15:31 Так буквально все понимать - так у вас год объект на год объекте будет.
Я привел Information Expert затем, чтобы объяснить почему метод login() поместили в форму, чем могли руководствоваться. Что LoginForm резко стал God Object после добавления метода login()? Конечно, GRASP необходимо применять, опираясь на все принципы. В GRASP помимо Information Expert есть еще Low Coupling и High Cohesion и др. Руководствуясь GRASP в комплексе God Object никогда не получится.

Если следовать SRP с пеной у рта, то ООП выродится в процедурный стиль. Будут объекты с данными и методами доступа к ним и объекты с одним методом execute() или что-то типа того, по которым будет раскидано все настоящее поведение системы.
noLogicOnlyWar писал(а): 2018.03.23, 15:31 Откуда у вас в методе формы $user появляется?
Я пример взял у ТС. Конечно $user берется из какого-нибудь DAO, Table или Repository. который внедряется в форму. Я бы написал, что-то типа этого:

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


class LoginForm extends Model
{

     public $login;
     
     public $password;

     private $users_table;
     
     private $web_user;
     
     public function init()
     {
         //некоторые пользуются здесь Instanse::ensure() вместо обращения к контейнеру напрямую, что тоже можно
         $this->users_table = \Yii::$container->get(UsersTable::class);
         $this->web_user = \Yii::$app->user;
         parent::init();
     }
     
     
   
     public function login()
     {
       
        if (!$this->validate())
             throw new DomainException();
             //можно и return false, если так привычнее
             
     
     	$user = $this->users_table->finOneByLogin($this->login);
     	
     	if ($user === null)
            throw new \DomainException();
            
        $this->web_user->login($user);     
     	
     }
     
     
     //здесь будут rules, в которых проверится верность логина/пароля и пр и пр.
     

}

noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: Form, Model, AR

Сообщение noLogicOnlyWar »

Ну так я поэтому и спросил про $user. Вы нарушаете вполне четко сформулированный srp, но зато не нарушаете IE который можно интерпретировать по разному в данной ситуации. Давайте тогда всю бизнес логику прямо в dto будем класть чего уж там.
Если следовать SRP с пеной у рта, то ООП выродится в процедурный стиль. Будут объекты с данными и методами доступа к ним и объекты с одним методом execute() или что-то типа того, по которым будет раскидано все настоящее поведение системы.
Слепое следование любым правилам ни к чему хорошему не приводит. У меня например сложилось такое впечатление что вы решили что solid не тру, а grasp тру. В результате нарушаете первое прикрываясь вторым, вместо поиска компромисса.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Form, Model, AR

Сообщение anton_z »

noLogicOnlyWar писал(а): 2018.03.24, 16:12 Ну так я поэтому и спросил про $user. Вы нарушаете вполне четко сформулированный srp.
Так уж четко сформулированный и так и нарушаю? А можно узнать, что такое "единственная обязанность"? что такое "одна причина для изменения"? Можно определение, не пример, а определение. Как эту причину выделить?

Я могу обязанность формы LoginForm определить как "залогинить юзера", валидация логина и пароля, а также обращение за экземпляром $user под это подпадает? Будет ли это пресловутой "единственной обязанностью" и если нет, то почему?

Как определить уровень детализации который нужно использовать, чтобы отделить одну обязанность от другой? На основе чего решить, является ли валидация логина и пароля одной, а обращение за учетной записью к соответствующем объекту другой обязанностью, или что они обе являются частью обязанности "залогинить юзера"? Я нахожу все приводимые объяснения недостаточно убедительными, поэтому я не считаю, что SRP сформулирован четко. А если определить обязанность LoginForm как "залогинить юзера" в приведенной интерпретации, то я ничего и не нарушил.
noLogicOnlyWar писал(а): 2018.03.24, 16:12 В результате нарушаете первое прикрываясь вторым, вместо поиска компромисса.
Почему нужно искать какие-то компромиссы? LoginForm получился сильно связанным, не тестируемым? Чем он плох?

Я не говорю, что в SOLIG все полный бред, и надо про него забыть и не пользоваться, просто у меня к нему есть вопросы.
noLogicOnlyWar писал(а): 2018.03.24, 16:12 но зато не нарушаете IE который можно интерпретировать по разному в данной ситуации.
А как бы вы его интерпретировали?
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: Form, Model, AR

Сообщение noLogicOnlyWar »

Как эту причину выделить?
Смотрите на класс который у вас вышел, прикидываете причины по которым полезете внутрь для изменения. В данном примере в 2х случаях -изменить правила валидации, изменить процедуру логина.
Так уж четко сформулированный и так и нарушаю? А можно узнать, что такое "единственная обязанность"? что такое "одна причина для изменения"? Можно определение, не пример, а определение.
Вы какой то скрытый смысл ищете? Я его не знаю, в противном случае могу дать ссылку на словарь Даля.
Как определить уровень детализации который нужно использовать, чтобы отделить одну обязанность от другой? На основе чего решить, является ли валидация логина и пароля одной, а обращение за учетной записью к соответствующем объекту другой обязанностью, или что они обе являются частью обязанности "залогинить юзера"?
Так я вам встречных вопросов накидаю. У репозитория доступ к хранилищу юзеров, в форме введенные данные, у чего то еще доступ к сессии - как мне решить какие из этих данных "ключивее" остальных для логина?
Я могу обязанность формы LoginForm определить как "залогинить юзера", валидация логина и пароля, а также обращение за экземпляром $user под это подпадает? Будет ли это пресловутой "единственной обязанностью" и если нет, то почему?
Каким образом правило "в email должна быть @" относится к процедуре логина? Какое это имеет значение если логин производится по токену в рест апи?
А как бы вы его интерпретировали?
Использовал бы AuthService (да, да, я знаю что вы напишите). Так как он бы у меня находился в домене он бы мог и исключение доменное бросить и доменное событие, и репозиторий (или какая другая абстракция над хранилищем) рядом лежит.
А если определить обязанность LoginForm как "залогинить юзера" в приведенной интерпретации, то я ничего и не нарушил.
Вот честно, по вашему LoginForm это то место где программист ожидаете найти метод login?
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Form, Model, AR

Сообщение anton_z »

noLogicOnlyWar писал(а): 2018.03.24, 18:50
Как эту причину выделить?
Смотрите на класс который у вас вышел, прикидываете причины по которым полезете внутрь для изменения. В данном примере в 2х случаях -изменить правила валидации, изменить процедуру логина.
И это четкая формулировка?

У меня в форме есть валидация и метод getData() который создает и возвращает DTO. Я прикидываю, что могу полезть туда для изменения правил ваилидации и кода создания DTO. Получается, я нарушил SRP по вашему определению.
noLogicOnlyWar писал(а): 2018.03.24, 18:50
Вы какой то скрытый смысл ищете? Я его не знаю, в противном случае могу дать ссылку на словарь Даля.
Чтобы использовать SRP нужно понять, что он значит. Для этого я привел вопросы, на которые заставляют меня сомневаться в годности SRP. Вместо ответов я вижу что-то про скрытый смысл и общелексический словарь. Ничего из перечисленного не может помочь ответить на приведенные мной вопросы.
noLogicOnlyWar писал(а): 2018.03.24, 18:50
Каким образом правило "в email должна быть @" относится к процедуре логина? Какое это имеет значение если логин производится по токену в рест апи?
Зависит от того, что считать "процедурой логина".
noLogicOnlyWar писал(а): 2018.03.24, 18:50
Так я вам встречных вопросов накидаю. У репозитория доступ к хранилищу юзеров, в форме введенные данные, у чего то еще доступ к сессии - как мне решить какие из этих данных "ключивее" остальных для логина?
Да, согласно IE можно найти несколько классов-кандидатов на назначение какого-либо поведения. Решение об этом назначении принимается не только на основе Information Expert, а всех принципов GRASP и текстологического анализа. Они то как раз и помогут определить, какой из объектов обладающих кокой-либо частью данных для совершения операции лучше всего подходит для назначения того или иного поведения.
noLogicOnlyWar писал(а): 2018.03.24, 18:50 Использовал бы AuthService (да, да, я знаю что вы напишите). Так как он бы у меня находился в домене он бы мог и исключение доменное бросить и доменное событие, и репозиторий (или какая другая абстракция над хранилищем) рядом лежит.
Да, разделение на сервисы и сущности это процедурная парадигма в новом обличье. Пользоваться процедурной парадигмой нормально. Я нахожу ее неудобной для решения моих задач.
noLogicOnlyWar писал(а): 2018.03.24, 18:50 Вот честно, по вашему LoginForm это то место где программист ожидаете найти метод login?
Видимо да, если даже в примерах и шаблонах Yii так пишут.
noLogicOnlyWar писал(а): 2018.03.24, 18:50
Так я вам встречных вопросов накидаю.
Я не хочу холиварить и пускаться в пространные рассуждения. Я всего лишь хотел объяснить, что то, что написано в примере Yii (LoginForm::login()) не плохо и почему. Вы говорите, что это и мой пример нарушают SRP. Ну так давайте SRP обсуждать, а не IE, для меня именно это интересно - что конкретно я нарушил и что нарушено в LoginForm из Yii. Если Вы приведете понятное определение SRP, к оторому не будет вопросов, я Вам только спасибо скажу.
Последний раз редактировалось anton_z 2018.03.25, 03:13, всего редактировалось 1 раз.
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: Form, Model, AR

Сообщение noLogicOnlyWar »

И это четкая формулировка?
А вам нужна копипаста из гугла?
У меня в форме есть валидация и метод getData() который создает и возвращает DTO. Я прикидываю, что могу полезть туда для изменения правил ваилидации и кода создания DTO. Получается, я нарушил SRP по вышему определению.
Нет я не считаю так, создание дто тривиальная операция, и в данном случае я бы не стал строго придерживаться буквы S. Хотя я предпочитаю создавать дто в контроллере по данным из формы.
Это как то противоречит тому что я выше сказал? Я как бы сразу написал, что не считаю хорошей идеей слепо следовать какой либо из букв.
Чтобы использовать SRP нужно понять, что он значит. Для этого я привел вопросы. Вместо этого я вижу что-то про скрытый смысл и общелексический словарь. Ничего из перечисленного не может помочь ответить на приведенные мной вопросы. Больше нечего написать?
Нечего, есть принцип есть его определение. Вы просите дать определение определению, спасибо я пас.
Зависит от того, что считать "процедурой логина".
Окей, придрались ко словам законно. С моей стороны правильно было бы сказать о процедуре идентификации.
Мда, видимо я не совсем точно сформулировал IE, а вы о нем узнали из моего комментария. "Обладает данными" означает "имеет атрибуты". Какими атрибутами обладает AuthService и почему вы решили, что он Information Expert?
Ну конечно, спасибо что открыли глаза. "Обладает данными означает имеет атрибуты" - пруф пожалуйста. Сами то верите в такую трактовку?
Видимо да, если даже в примерах и шаблонах Yii так пишут.
Лень искать, но сами авторы фреймворка говорили о том что стартовые шаблоны стоит переделать. В том числе и из за bad practice.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Form, Model, AR

Сообщение anton_z »

noLogicOnlyWar писал(а): 2018.03.25, 03:13
Нечего, есть принцип есть его определение. Вы просите дать определение определению, спасибо я пас.
anton_z писал(а): 2018.03.24, 16:34 Как определить уровень детализации который нужно использовать, чтобы отделить одну обязанность от другой? На основе чего решить, является ли валидация логина и пароля одной, а обращение за учетной записью к соответствующем объекту другой обязанностью, или что они обе являются частью более крупной обязанности "залогинить юзера"?
На это можете ответить, раз Вы четко понимаете определение SRP? Как все-таки правильно отделить одну обязанность от другой?
По мне так если определение не позволяет отделить одно от другого - такое определение не вообще ничего не стоит.
noLogicOnlyWar писал(а): 2018.03.25, 03:13
Ну конечно, спасибо что открыли глаза. "Обладает данными означает имеет атрибуты" - пруф пожалуйста. Сами то верите в такую трактовку?
Здесь да, согласен, ошибся. действительно просто "обладает данными". На все остальные претензии к GRASP ответил выше.
Последний раз редактировалось anton_z 2018.03.25, 05:21, всего редактировалось 5 раз.
Ответить