Реализация пользователей должна отвечать следующим требованиям:
При помощи класса UserUniqueFieldRule мы будем проверять наличие одного уникального пользователя со значением какого-либо поля. Это правило валидации потом будет использоваться в классе User.
Файл shop/src/validation/UserUniqueFieldRule.class.php:
<?php lmb_require('limb/validation/src/rule/lmbSingleFieldRule.class.php'); lmb_require('limb/dbal/src/criteria/lmbSQLFieldCriteria.class.php'); lmb_require('src/model/User.class.php'); class UserUniqueFieldRule extends lmbSingleFieldRule { protected $current_user; function __construct($field, $current_user) { $this->current_user = $current_user; parent :: __construct($field); } function check($value) { $user = new User(); $records = $user->findAllRecords(new lmbSQLFieldCriteria($this->field_name, $value)); if($records->count() && ($this->current_user->isNew() || $this->_sameFieldUserRecordExists($records))) $this->error('User with the same value of {Field} already exists'); } function _sameFieldUserRecordExists($records) { foreach($records as $record) { if($record->get('id') != $this->current_user->getId()) return true; } return false; } } ?>
Класс UserUniqueFieldRule получает в конструкторе название поля, которое должно быть уникальным, и ссылку на текущего пользователя. Текущий пользователь понадобится нам при редактировании.
Класс lmbSingleFieldRule является базовым для правил валидации одного поля. Файл класса можно найти по пути limb/validation/src/rule/lmbSingleFieldRule.class.php Дочерние классы должны перекрывать метод check($value), где $value - значение поля, которое необходимо проверить. Следует отметить, что check($value) не вызывается, если $value не содержит значимого значения. Поэтому совместно с этим правилом (его дочерними) рекомендуется использовать также правило lmbRequiredFieldRule. Об этом ты также упомянем чуть ниже при описании кода класса User.
При помощи find-метода класса lmbActiveRecord делается запрос к базе данных. В качестве второго параметра вместо $params можно передавать объект критерии, которая будет накладываться на выборку. Критерия - это объектная форма условия. В нашем случае использовался класс lmbSQLFieldCriteria, который принимает в конструкторе название поля и значение (по-умолчанию для этой пары формируется условие равенства). Использование критериев позволяет не думать о экранировании данных в SQL-запросах. Вы можете подробнее ознакомиться с классами Criteria, однако это необязательно для понимания данного примера.
Метод lmbSingleFieldRule :: error($error_string, $values = array()) добавляет ошибку в список ошибок валидации. Выражения вида {Field} будут заменены в процессе работы на реальные имена полей формы, с которой производится создание/редактирование пользователя.
Теперь можно написать код класса User.
Файл shop/src/model/User.class.php:
<?php class User extends lmbActiveRecord { protected $_default_sort_params = array('name' => 'ASC'); protected $password; protected function _createValidator() { $validator = new lmbValidator(); $validator->addRequiredRule('login'); $validator->addRequiredRule('email'); $validator->addRequiredRule('name'); lmb_require('src/validation/UserUniqueFieldRule.class.php'); $validator->addRule(new UserUniqueFieldRule('login', $this)); $validator->addRule(new UserUniqueFieldRule('email', $this)); lmb_require('limb/validation/src/rule/lmbEmailRule.class.php'); $validator->addRule(new lmbEmailRule('email')); return $validator; } protected function _onBeforeSave() { $this->_generatePassword(); } protected function _generatePassword() { if($this->password) $this->setHashedPassword(self :: cryptPassword($this->password)); } static function cryptPassword($password) { return md5($password); } } ?>
Создаваемый в методе _createValidator() валидатор проверяет, что поля email и login должны быть заполнены и иметь уникальные значения. Последнее обеспечивается за счет использования правила UserUniqueFieldRule. Правило lmbEmailRule удостоверяется, что введенное в поле значение является электронным адресом.
Обратите внимание на метод _onBeforeSave(). Это так называемый метод расширения класса lmbActiveRecord, который дочерние классы могут использовать чтобы выполнять необходимые действия, например, перед сохранением новой записи, перед обновлением существующей, после сохранения и т.д. Список методов расширения класса lmbActiveRecord есть в разделе "Расширение поведения базового класса lmbActiveRecord".
В нашем случае мы использовали этот метод, чтобы шифровать поле password и формировать значение поля hashed_password. Таким образом мы будем хранить только хешированный пароль, а обновление хешированного пароля будет происходить только, если в поле password есть новое значение.
Надеемся, что это часть не составит для Вас никакого труда. Новые для вас моменты должны быть в реализации действий по созданию нового пользователя и смене пароля в контроллере AdminUserController. Там мы будем использовать дополнительный валидатор для проверки полей с паролями. Получится, что в итоге данные с одной формы будут валидироваться дважды - один раз в контроллере, другой раз в ActiveRecord-е.
Файл shop/template/admin_user/create.html:
<core:set title='Create user'/> <core:wrap file="admin_page.html" in="content"> <form id='user_form' name='user_form' method='post' runat='server'> <core:include file="form_errors.html" /> <div class="field"> <label for="password">Password:</label> <input name="password" type="password" title="Password" class="input"/> </div> <div class="field"> <label for="repeat_password">Repeat password:</label> <input name="repeat_password" type="password" title="Repeat password" class="input"/> </div> <core:include file='admin_user/form_fields.html'/> <input name='create' type='submit' value='Create' runat='client'/> </form> </core:wrap>
Файл shop/template/admin_user/edit.html:
<core:set title='Edit user'/> <core:wrap file="admin_page.html" in="content"> <form id='user_form' name='user_form' method='post' runat='server'> <core:include file="form_errors.html" /> <core:include file='admin_user/form_fields.html'/> <input name='edit' type='submit' value='Edit' runat='client'/> </form> </core:wrap>
Файл shop/template/admin_user/form_fields.html:
<div class="field"> <label for="login">Login:</label> <input name="login" type="text" title="Login" class="input"/> </div> <div class="field"> <label for="name">Name:</label> <input name="name" type="text" title="Name" class="input"/> </div> <div class="field"> <label for="email">Email:</label> <input name="email" type="text" title="Email" class="input"/> </div> <div class="field"> <label for="email">Is admin:</label> <js_checkbox id='is_admin' name='is_admin' class="input"/> </div> <div class="hr"></div>
Файл shop/templates/admin_user/display.html:
<core:set title='Users'/> <core:wrap file="admin_page.html" in="content"> <route_url params='action:create'>Create user</route_url> <active_record:fetch using='src/model/User' target="users" navigator='pager' /> <core:include file='pager.html'/> <list:list id='users'> <table cellspacing=0 cellpadding=0 class='list'> <thead> <tr> <th>Name</th> <th>Login</th> <th>E-mail</th> <th>Actions</th> </tr> </thead> <tbody> <list:item> <tr class='{$Parity}'> <td>{$name}</td> <td>{$login}</td> <td><a href='mailto:{$email}'>{$email}</a></td> <td> <route_url params='action:change_password,id:{$id}'>Change password</route_url> <route_url params='action:edit,id:{$id}'>Edit</route_url> <route_url params='action:delete,id:{$id}'>Delete</route_url> </td> </tr> </list:item> </tbody> </table> </list:list> </core:wrap>
При реализации контроллера AdminUserController мы также создадим общий метод _performCreateOrEdit(), как и в AdminProductController, который будет отвечать за создание и редактирование учетных записей пользователей.
Дополнительно вы введем проверку при создании пользователя, что значения полей password и repeat_password должны совпадать. Так мы покажем как можно валидировать данные, пришедшие с форм, прямо в контроллере.
Файл shop/src/controller/AdminUserController.class.php:
<?php lmb_require('src/model/User.class.php'); class AdminUserController extends lmbController { function doCreate() { $this->_performCreateOrEdit(); } function doEdit() { $this->_performCreateOrEdit(); } protected function _performCreateOrEdit() { if(!$id = $this->request->getInteger('id')) $id = null; $item = new User($id); $this->useForm('user_form'); $this->setFormDatasource($item); if($this->request->hasPost()) { $item->import($this->request); if($this->request->get('create')) $this->_validatePasswordField(); if($item->trySave($this->error_list)) $this->redirect(); } } function doDelete() { $item = new User($this->request->getInteger('id')); $item->destroy(); $this->redirect(); } function _validatePasswordField() { $this->validator->addRequiredRule('password'); $this->validator->addRequiredRule('repeat_password'); lmb_require('limb/validation/src/rule/lmbMatchRule.class.php'); $this->validator->addRule(new lmbMatchRule('password', 'repeat_password')); $this->validator->validate($this->request); } } ?>
Рассмотрим метод _validatePasswordField(). Класс lmbController по-умолчанию содержит объект валидатора $validator, которому передали список ошибок $error_list. В методе мы добавили в этот валидатор три правила:
Метод lmbValidator :: validate($datasource) проверяет переданный контейнер данных $datasource при помощи ранее добавленных в него правил. В нашем случае мы отдали на проверку объект Запроса $request.
Теперь можете попробовать создать уникальных пользователей, пользователей с дублирующимися логинами и т.д.
Добавим также действие по смене пароля, скажем change_password. Текущий зарегистированный администратор сможет легко менять пароли пользователей. Для этого нам необходимо будет добавить новый метод doChangePassword() в AdminUserController и новый шаблон change_password.html:
Файл shop/template/admin_user/change_password.html:
<core:set title='Change user password'/> <core:wrap file='admin_page.html' in='content'> <form method="post" name="user_form" id="user_form" runat="server"> <core:include file='form_errors.html'/> <div class='field'> <label for="password">New password:</label> <input name="password" type="password" title="New password" class="input"/> </div> <div class='field'> <label for="repeat_password">Repeat password:</label> <input name="repeat_password" type="password" title="Repeat password" class="input"/> </div> <input type="submit" name="submitted" value="Change password"/> </form> </core:wrap>
Изменения в файле shop/src/controller/AdminUserController.html:
class AdminUserController extends lmbController { [...] function doChangePassword() { if(!$this->request->hasPost()) return; $this->useForm('user_form'); $this->setFormDatasource($this->request); $this->_validatePasswordField(); if(!$this->error_list->isValid()) return; $user = new User($this->request->getInteger('id')); $user->setPassword($this->request->get('password')); if($user->trySave($this->error_list)) $this->redirect(); } [...] }
Здесь мы также использовали метод _validatePasswordField() для проверки полей для ввода паролей.
Мы сделали модуль по управлению пользователей. Мы рекомендуем вам при помощи этой новой функциональности добавить себе пользователя с установленных флагом «Is Admin» и приступить к следующему шагу: "Шаг4.2 Аутентификация пользователей".
Обсуждение