Сервис на Yii2: Просмотр и редактирование профиля
В прошлой части мы исправляли и переводили интерфейс нашего приложения. Сегодня мы, как анонсировалось ранее, добавим страницу просмотра профиля пользователя и выведем формы для редактирования информации о пользователе и для смены пароля.
Часть 1: Установка и настройка приложения
Часть 2: Настройка IDE и модульная структура
Часть 3: Перенос пользователей в БД
Часть 4: Доработка шаблона и локализация
Для удобства изучения сходники проекта теперь доступны на GitHub.
По просьбам читателей в комментариях и в обратной связи немного исправил предыдущую часть, добавив использование компонента интернационализации для перевода интерфейса.
Итак, приступим. В модуле user
на данный момент имеется только контроллер DefaultController
.
Дадим пользователю возможность просматривать и редактировать свой профиль. Для этого добавим новый контроллер ProfileController
, доступный только для авторизованных пользователей. Сделаем его пока с единственным действием index
. В этом действии мы получаем модель текущего пользователя и передаём в представление:
namespace app\modules\user\controllers; use app\modules\user\models\User; use yii\filters\AccessControl; use yii\web\Controller; use Yii; class ProfileController extends Controller { public function behaviors() { return [ 'access' => [ 'class' => AccessControl::className(), 'rules' => [ [ 'allow' => true, 'roles' => ['@'], ], ], ], ]; } public function actionIndex() { return $this->render('index', [ 'model' => $this->findModel(), ]); } /** * @return User the loaded model */ private function findModel() { return User::findOne(Yii::$app->user->identity->getId()); } }
Вспомним, что наша модель User
реализовывает интерфейс IdentityInterface
и её класс указан в качестве identityClass
для компонента Yii::$app->user
:
$config = [ ... 'components' => [ 'user' => [ 'identityClass' => 'app\modules\user\models\User', 'enableAutoLogin' => true, ], ... ], ];
и при авторизации в качестве Yii::$app->user->identity
используется именно экземпляр модели User
текущего пользователя. Это значит, что в качестве $model мы могли бы использовать прямо этот экземпляр Yii::$app->user->identity
. Но безопаснее в ходе выполнения действия контроллера не давать доступа к самому экземпляру Yii::$app->user->identity
. Поэтому мы ещё раз загрузили экземпляр пользователя из базы данных:
return User::findOne(Yii::$app->user->identity->getId());
При этом не возникнет проблем с пустым identity
, так как с помощью поведения AccessControl
мы разрешаем доступ ко всем действиям этого контроллера только авторизованным пользователям.
В представлении modules/user/views/profile/index.php
будем просто выводить информацию о пользователе с помощью виджета DetailView
:
<?php use yii\helpers\Html; use yii\widgets\DetailView; /* @var $this yii\web\View */ /* @var $model app\modules\user\models\User */ $this->title = Yii::t('app', 'TITLE_PROFILE'); $this->params['breadcrumbs'][] = $this->title; <div class="user-profile"> <h1> Html::encode($this->title) </h1> DetailView::widget([ 'model' => $model, 'attributes' => [ 'username', 'email', ], ]) </div>
Теперь мы можем попробовать отобразить профиль. Для удобства добавим в меню пункт Профиль
:
echo Nav::widget([ 'options' => ['class' => 'navbar-nav navbar-right'], 'items' => array_filter([ ['label' => Yii::t('app', 'NAV_HOME'), 'url' => ['/main/default/index']], ['label' => Yii::t('app', 'NAW_CONTACT'), 'url' => ['/main/contact/index']], Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'NAV_SIGNUP'), 'url' => ['/user/default/signup']] : false, Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'NAV_LOGIN'), 'url' => ['/user/default/login']] : false, !Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'NAV_PROFILE'), 'url' => ['/user/profile/index']] : false, !Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'NAV_LOGOUT'), 'url' => ['/user/default/logout'], 'linkOptions' => ['data-method' => 'post']] : false, ]), ]);
и не забудем добавлять переводы по мере необходимости в файлы в созданных на прошлом уроке папках messages/en
и messages/ru
.
С нашими правилами маршрутизации эта страница будет доступна по адресу /user/profile
. Мы могли бы не создавать отдельный контроллер ProfileController
и поместить этот функционал прямо в DefaultController::actionIndex
, чтобы профиль отображался по адресу /user
. Но для нашего удобства всё-таки вынесли. Иначе DefaultController
окажется слишком большим.
Давайте ещё сделаем так, чтобы с пока нерабочего (так как действия actionIndex
в DefaultController
пока нет) адреса /user
нас автоматически перекидывало на /user/profile
. Просто добавим действие и поместим в него перенаправление на соседний контроллер:
namespace app\modules\user\controllers; ... class DefaultController extends Controller { ... public function actionIndex() { return $this->redirect(['profile/index'], 301); } }
Теперь при ручном переходе по адресу /user
не вывалится ошибка, а сработает редирект на профиль:
Страница просмотра готова. Добавим теперь страницу редактирования.
Редактирование: сценарии и формы
Итак, наша текущая модель пользователя хранит правила валидации для имени пользователя, адреса электронной почты и статуса:
class User extends ActiveRecord implements IdentityInterface { ... public function rules() { return [ ['username', 'required'], ['username', 'match', 'pattern' => '#^[\w_-]+$#i'], ['username', 'unique', 'targetClass' => self::className(), 'message' => Yii::t('app', 'ERROR_USERNAME_EXISTS')], ['username', 'string', 'min' => 2, 'max' => 255], ['email', 'required'], ['email', 'email'], ['email', 'unique', 'targetClass' => self::className(), 'message' => Yii::t('app', 'ERROR_EMAIL_EXISTS')], ['email', 'string', 'max' => 255], ['status', 'integer'], ['status', 'default', 'value' => self::STATUS_ACTIVE], ['status', 'in', 'range' => array_keys(self::getStatusesArray())], ]; } ... }
Нашу модель можно выводить в форму редактирования с любыми полями из этих. Например, мы могли бы вывести форму с полем для адреса электронной почты, создав действие редактирования в контроллере ProfileController
:
class ProfileController extends Controller { ... public function actionUpdate() { $model = $this->findModel(); if ($model->load(Yii::$app->request->post()) && $model->save()) { return $this->redirect(['index']); } else { return $this->render('update', [ 'model' => $model, ]); } } ... }
и сделав для него представление с формой:
... <?php $form = ActiveForm::begin([]); $form->field($model, 'email')->input('email', ['maxlength' => true]) <div class="form-group"> Html::submitButton(Yii::t('app', 'BUTTON_SAVE'), ['class' => 'btn btn-primary']) </div> ActiveForm::end(); ...
Но есть нюанс:
Из формы будут приниматься все перечисленные в
rules()
поля. И если пользователь с помощью инструментов разработчика в браузере добавит в форму полеUser[status]
илиUser[username]
, то может изменить недоступные ему поля. Но нам нужно сохранить эти поля для редактирования в панели администратора.
Для решения этой проблемы можно пойти двумя путями. Их мы и рассмотрим.
Сценарии модели
Любая модель в Yii2 позволяет задавать ей несколько сценариев использования, для которых можно задавать разные правила валидации и для смены поведения.
Нам нужно ввести два сценария: один для администратора (со всеми правилами) и второй для пользователя в его профиле. По умолчанию в модели все правила рассматриваются для сценария default
, хранящегося в константе Model::SCENARIO_DEFAULT
. Так что нам остаётся добавить один сценарий profile
. Обозначим его также константой User::SCENARIO_PROFILE
:
class User extends ActiveRecord implements IdentityInterface { const SCENARIO_PROFILE = 'profile'; ... }
Теперь нам нужно определить, какие правила будут работать у администратора, а какие на странице профиля пользователя. Можно пойти путём Yii1
, введя в специфические правила указания применимости on
к сценарию либо исключения except
:
class User extends ActiveRecord implements IdentityInterface { const SCENARIO_PROFILE = 'profile'; ... public function rules() { return [ ... ['email', 'required', 'except' => self::SCENARIO_PROFILE], ['email', 'email', 'except' => self::SCENARIO_PROFILE], ['email', 'unique', 'targetClass' => self::className(), 'except' => self::SCENARIO_PROFILE, 'message' => Yii::t('app', 'ERROR_EMAIL_EXISTS')], ['email', 'string', 'max' => 255, 'except' => self::SCENARIO_PROFILE], ... ]; } }
И фреймворк для сканирования атрибутов запустит метод scenarios()
модели, который обойдёт все правила и построит список сценариев и полей для них.
Но это не очень удобно с большим количеством правил. Намного удобнее оставить правила как есть и самому переопределить метод scenarios()
, где вручную перечислить нужные для каждого сценария поля:
class User extends ActiveRecord implements IdentityInterface { const SCENARIO_PROFILE = 'profile'; ... public function scenarios() { return [ self::SCENARIO_DEFAULT => ['username', 'email', 'status'], self::SCENARIO_PROFILE => ['email'], ]; } }
или аналогично:
class User extends ActiveRecord implements IdentityInterface { const SCENARIO_PROFILE = 'profile'; ... public function scenarios() { return ArrayHelper::merge(parent::scenarios(), [ self::SCENARIO_PROFILE => ['email'], ]); } }
Этими строками мы разрешим пользователю редактировать только адрес электронной почты. А все остальные поля разрешим менять в будущем только администратору.
Теперь в действии редактирования профиля не забудем указать модели нужный нам сценарий:
namespace app\modules\user\controllers; use app\modules\user\models\User; use yii\filters\AccessControl; use yii\web\Controller; use Yii; use yii\widgets\ActiveForm; class ProfileController extends Controller { ... public function actionUpdate() { $model = $this->findModel(); $model->scenario = User::SCENARIO_PROFILE; if ($model->load(Yii::$app->request->post()) && $model->save()) { return $this->redirect(['index']); } else { return $this->render('update', [ 'model' => $model, ]); } } ... }
И добавим само представление modules/user/views/profile/update.php
с формой:
<?php use yii\bootstrap\ActiveForm; use yii\helpers\Html; /* @var $this yii\web\View */ /* @var $model app\modules\user\models\User */ $this->title = Yii::t('app', 'TITLE_UPDATE'); $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'TITLE_PROFILE'), 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; <div class="user-profile-update"> <h1> Html::encode($this->title) </h1> <div class="user-form"> $form = ActiveForm::begin(); $form->field($model, 'email')->textInput(['maxlength' => true]) <div class="form-group"> Html::submitButton(Yii::t('app', 'BUTTON_SAVE'), ['class' => 'btn btn-primary']) </div> ActiveForm::end(); </div> </div>
Пока у нас здесь всего одно поле:
Но в будущем можем реализовать и другие настройки профиля.
Теперь рассмотрим другой подход.
Модели форм
Использовать сценарии удобно, но это немного захламляет модель. Если мы вспомним наши страницы логина, регистрации, восстановления пароля, то для них почему-то добавлены отдельные модели LoginForm, SignupForm и PasswordResetForm.
Но ведь, по сути, это относится к модели пользователя. Их бы можно было реализовать с помощью единственной модели User
со сценариями SCENARIO_LOGIN
, SCENARIO_SIGNUP
и подобными. Но кроме различия правил валидации в этих моделях есть свои специфические методы login()
, signup()
и прочие, которые нужны только для одного действия.
Если бы мы собрали все эти сценарии использования в одной модели User
, то там оказалось бы слишком много вспомогательного кода для реализации всех случаев жизни. И, в итоге, наша модель из простой записи ActiveRecord
превратилась бы в большой комбайн с десятками и сотнями методов и вспомогательных полей.
Именно поэтому ради упрощения кода для реализации специфичных форм желательно создавать отдельные модели.
Это, во первых, позволяет получить несколько простых и «чистых» классов.
Во-вторых, повышает безопасность, так как при описании сценариев можно нечаянно ошибиться и открыть какое-нибудь служебное поле, а при использовании отдельного класса пользователь не имеет прямого доступа к редактированию исходной модели User
.
И, в третьих, внутри такой отдельной модели удобно добавлять любой код, любые public
поля, оперировать любыми другими сущностями. Например, подписать пользователя на рассылку, если он поставил галочку в поле subscribe
. Помещать это всё в одну модель User
было бы слишком громоздко.
Так что добавим новую форму для редактирования данных пользователя с полем $email
:
namespace app\modules\user\models; use yii\base\Model; use yii\db\ActiveQuery; use Yii; class ProfileUpdateForm extends Model { public $email; /** * @var User */ private $_user; public function __construct(User $user, $config = []) { $this->_user = $user; $this->email = $user->email; parent::__construct($config); } public function rules() { return [ ['email', 'required'], ['email', 'email'], [ 'email', 'unique', 'targetClass' => User::className(), 'message' => Yii::t('app', 'ERROR_EMAIL_EXISTS'), 'filter' => ['<>', 'id', $this->_user->id], ], ['email', 'string', 'max' => 255], ]; } public function update() { if ($this->validate()) { $user = $this->_user; $user->email = $this->email; return $user->save(); } else { return false; } } }
У нас нет в базе отдельной таблицы profile
, так что мы будем использовать только модель User
. Мы переопределили конструктор так, чтобы получать эту модель. Поменяем теперь контроллер:
public function actionUpdate() { $user = $this->findModel(); $model = new ProfileUpdateForm($user); if ($model->load(Yii::$app->request->post()) && $model->update()) { return $this->redirect(['index']); } else { return $this->render('update', [ 'model' => $model, ]); } }
Всё. Сценарий SCENARIO_PROFILE
нам здесь больше не нужен.
Страница редактирования профиля готова.
Страница смены пароля
Чтобы не загромождать модель User
, сделаем смену пароля отдельной от редактирования профиля формой.
Для формы ввода создадим модель PasswordChangeForm
и добавим три поля для нового и текущего паролей и правила валидации для них. В эту модель через конструктор мы будем передавать модель пользователя, чей пароль нужно будет изменить. Для валидации текущего пароля создадим отдельный метод-валидатор currentPassword
, который будет проверять правильность пароля, вызывая метод validatePassword
переданной ему модели пользователя:
namespace app\modules\user\models; use yii\base\Model; use Yii; /** * Password reset form */ class PasswordChangeForm extends Model { public $currentPassword; public $newPassword; public $newPasswordRepeat; /** * @var User */ private $_user; /** * @param User $user * @param array $config */ public function __construct(User $user, $config = []) { $this->_user = $user; parent::__construct($config); } public function rules() { return [ [['currentPassword', 'newPassword', 'newPasswordRepeat'], 'required'], ['currentPassword', 'currentPassword'], ['newPassword', 'string', 'min' => 6], ['newPasswordRepeat', 'compare', 'compareAttribute' => 'newPassword'], ]; } public function attributeLabels() { return [ 'newPassword' => Yii::t('app', 'USER_NEW_PASSWORD'), 'newPasswordRepeat' => Yii::t('app', 'USER_REPEAT_PASSWORD'), 'currentPassword' => Yii::t('app', 'USER_CURRENT_PASSWORD'), ]; } /** * @param string $attribute * @param array $params */ public function currentPassword($attribute, $params) { if (!$this->hasErrors()) { if (!$this->_user->validatePassword($this->$attribute)) { $this->addError($attribute, Yii::t('app', 'ERROR_WRONG_CURRENT_PASSWORD')); } } } /** * @return boolean */ public function changePassword() { if ($this->validate()) { $user = $this->_user; $user->setPassword($this->newPassword); return $user->save(); } else { return false; } } }
Добавим действие смены пароля в наш контроллер:
namespace app\modules\user\controllers; use app\modules\user\models\PasswordChangeForm; use app\modules\user\models\User; use yii\filters\AccessControl; use yii\web\Controller; use Yii; class ProfileController extends Controller { ... public function actionPasswordChange() { $user = $this->findModel(); $model = new PasswordChangeForm($user); if ($model->load(Yii::$app->request->post()) && $model->changePassword()) { return $this->redirect(['index']); } else { return $this->render('passwordChange', [ 'model' => $model, ]); } } ... }
и добавим представление modules/user/views/profile/passwordChange.php
:
<?php use yii\bootstrap\ActiveForm; use yii\helpers\Html; /* @var $this yii\web\View */ /* @var $model app\modules\user\models\ChangePasswordForm */ $this->title = Yii::t('app', 'TITLE_PASSWORD_CHANGE'); $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'TITLE_PROFILE'), 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; <div class="user-profile-password-change"> <h1> Html::encode($this->title) </h1> <div class="user-form"> $form = ActiveForm::begin(); $form->field($model, 'currentPassword')->passwordInput(['maxlength' => true]) $form->field($model, 'newPassword')->passwordInput(['maxlength' => true]) $form->field($model, 'newPasswordRepeat')->passwordInput(['maxlength' => true]) <div class="form-group"> Html::submitButton(Yii::t('app', 'BUTTON_SAVE'), ['class' => 'btn btn-primary']) </div> ActiveForm::end(); </div> </div>
Страница смены пароля готова:
Теперь в соседнее представление просмотра профиля index.php
добавим ссылки на эти действия:
<?php use yii\helpers\Html; use yii\widgets\DetailView; /* @var $this yii\web\View */ /* @var $model app\modules\user\models\User */ $this->title = Yii::t('app', 'TITLE_PROFILE'); $this->params['breadcrumbs'][] = $this->title; <div class="user-profile"> <h1> Html::encode($this->title) </h1> <p> Html::a(Yii::t('app', 'BUTTON_UPDATE'), ['update'], ['class' => 'btn btn-primary']) Html::a(Yii::t('app', 'LINK_PASSWORD_CHANGE'), ['password-change'], ['class' => 'btn btn-primary']) </p> DetailView::widget([ 'model' => $model, 'attributes' => [ 'username', 'email', ], ]) </div>
В приложении теперь появилась страница просмотра профиля:
страница редактирования данных (пока у нас всего одно поле):
и страница смены пароля:
Вот и всё. А дальше:
Часть 6: Модуль администрирования и GridView
Подписывайтесь на рассылку (форма в сайдбаре), чтобы не пропустить продолжение и получать полезные фишки, и оставляйте свои предложения или замечания в комментариях. И оставляйте предложения по темам будущих вебинаров здесь. Спасибо!
Спасибо, хорошо пишешь., красавчик :)
Спасибо за статью. Продолжай в том же духе... По чаще бы статьи выходили. ;)
думаю логично было бы далее сделать модуль admin и вынести в него управление пользователями. Так же не помешало бы разобрать тему про RBAC
Полностью поддерживаю....
Спасибо огромное!
Спасибо как всегда приятно читать, Дмитрий вынесите профиль пользователя в отдельную таблицу пригодится, в основном всегда приходиться делать отдельную таблицу, как раз многим будет интересно, в гугле много запросов про то как сохранить связанные данные.
Здравствуйте, Дмитрий! А в какую папку будет разумнее кидать css, javascript'ы, следуя вашему шаблону?
в папку web/css, или создай свою папку themes с с темой + css, js
В моделе указано:
почему не self::STATUS_WAIT ?
Здесь без разницы.
Вопрос, а что передается в переменной params вот тут: validatePassword($attribute, $params) и зачем она там нужна, вроде ж она нигде не используется?
Это сигнатура написания встроенных валидаторов. При вызове с параметрами
как раз всё остальное попадёт в $params:
по этой функции понятно, спасибо. Еще в документации покурил по Yii1, там как раз код закомментирован в схожей функции.
А вот теперь задача, помогите разобраться. Решил на основе вашего примера реализовать смену пароля в модели User, а не в новой модели.
Кнотроллер:
Модель:
тут я думал, что пароль вводить при редактировании профиля нужно будет по-любому, а вот нового пароля может и не быть, поэтому убрал из правил обязательное условие заполнения полей нового пароля и перед сохранением проверяю есть ли что там чтобы понять нужно ли обновлять пароль.
Если новый пароль не заполнять, форма работает, поля обновляет, текущий пароль валидируется. А вот если новый пароль забить, то форма не проходит проверку текущего пароля (changeValidatePassword). Она ж должна тупо захешировать текущий и сравнить с таблицей. В поле пароль не меняется, проверил. Уже всю голову сломал, где я дурак?
отвечаю сам себе.
при валидации пароля хеш читается от нового, сначала срабатывает setPassword, а потом сравниваются хеши
Добрый день очень понравились курсы, хочу ими воспользоваться для работы над дипломным проектом.
Самая сложная часть была на прошлых курсах когда делали контроллеры и модели и самое обидное, что к данному курсу уже забываешь что делал на прошлых.
Можно ли сделать небольшой список с классами моделей и контроллеров, которые вы делали на прошлых занятиях и вкратце их функционал?
А Вы когда что-то делаете сразу конспектируйте, что именно делали. Тогда на следующем уроке сразу всё вспомните.
Для просмотра кода в каждый момент времени можете открыть историю изменений, найти нужную дату и щёлкнуть справа от любого изменения на кнопку просмотра именно того состояния. Или щёлкнуть прямо по названию коммита и просмотреть, что там поменялось.
А все классы и методы действуют именно так, как они и называются.
опечатка:
...Иначе DefaultDontroller окажется слишком большим.
Спасибо! Исправил.
Добавил модель ProfileUpdateForm.
Здравствуйте! Подскажите в чем может быть проблема? Делаю по вашему руководству. Возникает ошибка:
Значит пользователя не передаёте в new PasswordChangeForm($user).
Спасибо, уже разобрался!
Не понимаю зачем ради защиты от обновления лишних полей модели User, городить сценарии или отдельную модель формы, если в контроллере в actionUpdate вместо загрузки всех $_POST'ов
можно загружать только нужный
Или это слишком хардкодно?
Да, весьма хардкорно:
Особенно если полей много.
В разделе "Страница смены пароля" написано: "Для валидации текущего пароля создадим отдельный метод-валидатор currentPassword". Но метод-валидатор называется не currentPassword, а validatePassword
Спасибо. Исправил.
Добрый вечер Дмитрий. Подскажите пожалуйста как добавить поле для загрузки изображения. Рядом с редактированием email. Не загружаются оттуда картинки. Как я ни пытался. А так хотелось бы что бы в профиле у пользователя изображение тоже было
модель
view
вываливается ошибка SyntaxError: Unexpected token < in JSON at position 0.
Добавьте экшен 'upload' в actions() контроллера.
Действие происходит в update.php соответственно в контроллере в public function actionUpdate и если добавить actionUpload в контроллер, то это будет означать вью форму upload а не update. Ошибка возникает при сохранение самого файла.
в контроллере пробовал так
тут я эту проблему расскрыл полностью.
Буду очень признателен за помощь.
Переместите этот код в ProfileUpdateForm.
Здравствуйте Дмитрий. Есть небольшая проблема. Русский текст не записывается в БД. Хотя в конфиге стоит
и если заполняю поля на русском срабатывает условие else из контроллера то есть
но если пишу на английском то все нормально сохраняется. Не подскажете как это можно исправить?
Оказалось из-за этого условия
[['username', 'usersurname'], 'match', 'pattern' => '#^[\w_-]+$#i'],
И чем это лучше заменить? Или можно вообще убрать это условие?
Я убрал и у меня заработало, может это и неправильно но других идей не было
Добавить модификатор юникода в регулярное выражение.
Спасибо, за проделаную работу. Очень приятно почитать с утра
1. А что если просто наследовать новые формы от главной модели?
Например, форма регистрации наследуется от модели User, переназначает/дополняет метод rules(), реализовывает свои доп. методы и в конце нам нужно только вызвать $this->save(), если конечено в экшне указали, например, $form->signup();
2. Зачем файла называть в верблюжьем формате? Намного удобнее password-change.php
3. Хорошо бы еще подтвердить, что новая почта моя, одна опечатка и прощай акк.
Я сделал в качестве логина для входа почту. Теперь нужно сделать возможность для пользователя ее смены на другую. Как это правильно делать? Мне нужно еще добавлять поля в базу для токена новой почты? Нужно ли отправлять токен на старую почту?
Поле для новой почты и поле для токена.
Когда на почту приходит уведомление о комментариях с этого сайта, вместо названия всегда написано www-data.
Спасибо за статью, хотелось бы узнать как сделать чтобы для смены пароля отправлялась ссылка на e mail и только по приходу с нее можно было его изменить
Так уже сделано восстановление пароля.
Как хранить и редактировать дату рождения?
Вопрос, я решил пойти по такому пути, сделать одну форму для смены пароля, логина и роли и для админки и для профиля. Для того чтобы при редактировании передать данные в форму я в контролере сделал так
А саму форму сделал вот так
И вроде все работает? старые данные выводятся при редактировании и новые добавляться. Меня смущает два момента. 1) Валидация на уникальность НЕ срабатывает, точнее срабатывает но в этом месте
И я получается нечего не вижу что по сути не было сохранения.
и второй момент Для того чтобы передать данные форму я в конструкторе указал
А сами эти переменные публичные, так как приватные не будут работать в форме
Скажите я все правильно сделал или это какаято муть и компот)))? спасибо
Спасибо, полезный материал