====== Шаг4.2 Аутентификация пользователей ====== ===== Хранение данных пользователя в сессии ===== ==== Что такое toolkit? ==== Небольшое отступление... Некоторые объекты являются особо популярными, и зачастую требуется иметь глобальный доступ к таким объектам в рамках всей системы (приложения). К таким объектам можно отнести Запрос (Request), Ответ (Response), Пользователь (User), Подключения к базам данных (DbConnection), различные фабрики (Factory) и т.д. Есть несколько решений: * Использовать [[http://phpclub.ru/faq/wakka.php?wakka=DesignPatterns/Singleton|паттерн Singleton (одиночка)]] для реализации таких объектов. * Складывать такие объекты в общее хранище и передавать это хранилище между всеми объектами приложения. Часто такое решение называют контекст **Context**. В Limb выбран комбинированный вариант. Если просто, то мы создали только 1 одиночку, в которой хранятся все нужные в приложении объекты, и назвали ее Toolkit. Итак, **Toolkit (тулкит)** - это такой объект, через который можно получить какой-либо часто используемый или важный объект в любом месте программы. Тулкит реализован через класс **lmbToolkit** Для получения доступа к тулкиту используется метод **lmbToolkit :: instance()**. Пример использования тулкита: $toolkit = lmbToolkit :: instance(); $connection = $toolkit->getDefaultDbConnection(); Особенностью тулкита является то, что его интерфейс четко не определен, а зависит от так называемых инструментов - **tools**. Тулкит по сути состоит из наборов инструментов, вернее tools можно **регистрировать** в тулките. Именно tools определяют, что умеет тулкит (какие методы у него можно вызывать). Для регистрации нового набора инструментов в тулките существует метод lmbToolkit :: merge($tools). Например, создадим свой tools и зарегистрируем его в lmbToolkit: class MyTools extends lmbAbstractTools { function getSomethingUseful() { return [...]; } } lmbToolkit :: merge(new MyTools()); // :: instance() для добавления уже вызывать не нужно [...] // Где-то в клиентском коде $toolkit = lmbToolkit :: instance(); $my_object = $toolkit->getSomethingUseful(); Наборы инструментов можно заменять, то есть мы можем зарегистировать в тулките другой tools, который будет поддерживать интерфейс MyTools и для клиентского кода эта подмена будет совершенно прозрачной. Начальное заполнение тулкита наборами инструментов производится в файле limb/web_app/toolkit.inc.php. Этой информации пока нам вполне достаточно, чтобы понять наши дальнейшие действия. Более подробно о тулките в [[limb3_2007_2:ru:packages:toolkit|описании пакета Toolkit]]. Для нашего примера мы создадим наш собственный набор инструментов ShopTools и сделаем так, чтобы он поддерживал метод getUser(), который будет возвращать пользователя из сессии. ==== Работа с сессией в Limb ==== Limb не запрещает вам работать с сессиями через глобальную переменную $_SESSION, однако представляет другой, лучший с нашей точки зрения способ, со своими преимуществами. Для работы с сессией, в Limb есть специальный объект session, который можно получить через тулкит: $session = lmbToolkit :: instance()->getSession(); $session->set('my_var', $value); // Поставить значение в сессию $session->destroy('my_var'); // Удалить значение из сессии Работа с сессией осуществляется при помощи пакета SESSION. Преимущества использования пакета SESSION: * Возможность легкой смены способа хранения сессионных данных. На данный момент поддерживается хранение сессионных данных в базе данных и при помощи файлов. * Автоматическое подключение необходимых классов до десериализации сессии. Вам не нужно больше об этом заботиться. * Единый set/get интерфейс для установки и получения данных из сессии. Это позволяет в случае необходимости, например, передать сессию в WACT-шаблон в качестве контейнера с данными. * Легкое установление и сброс фикстур при модульном тестировании. ==== Хранение пользователя в тулките ==== Для того, чтобы хранить информацию о том, какой пользователь загеристрирован в приложении, нам необходимо хранить только его идентификатор. Этого будет вполне достаточно. А объект класса User мы будем хранить в ShopTools, которые мы зарегистрируем в тулките. Это позволит нам получить доступ к пользователю в любом месте программы. Итак, класс **ShopTools**. Файл shop/src/toolkit/ShopTools.class.php: user)) return $this->user; $session = lmbToolkit :: instance()->getSession(); if($user_id = $session->get('user_id')) { try { $this->user = new User($user_id); $this->user->setIsLoggedIn(true); } catch(lmbARException $e) { $this->user = new User(); $session->remove('user_id'); } } else $this->user = new User(); return $this->user; } } ?> Класс ShopUser наследуется от **lmbAbstractTools**, который используется в качестве базового для большинства наборов инструментов. lmbAbstractTools добавляет в тулкит поддержку всех методов класса (свои и методы дочернего класса), что нам в данном случае и нужно. Метод getUser() по сути реализует lazy loading для пользователя. Если идентификатор пользователя находится в сессии, значит пользователь был уже залогинен в приложении - и мы загружаем его запись из базы данных. Если же идентификатора пользователя в сессии нет - значит можно создать пустой объект класса User. Обратите внимание на блок try, catch, который ловит исключение класса lmbARException. Если пользователь с указанным удентификаторов был удален, то при инициализации User с идентификаторов, будет сгенерировано исключение lmbARException. В этом случае мы также должны инициализировать пустой объект класса User и удалить значение user_id из сессии. ==== Регистрация ShopTools в тулките ==== Наш набор инструментов ShopTools необходимо зарегистрировать в тулките. Обычно для этих целей создается файл **toolkit.inc.php** в корневой директории пакета или приложения. Файл shop/toolkit.inc.php: Добавим включение файла toolkit.inc.php в файл common.inc.php. Файл shop/common.inc.php: Теперь у нас есть все чтобы начать реализовывать процедуру аутентификации пользователей, отображение профайла на страницах сайта и проверку прав доступа к панели управления. ===== Аутентификация пользователей ===== ==== Изменения в классе User ==== Для начала внесем изменения в класс User, чтобы он хранил информацию о том, зарегистрирован ли пользователь или нет, а также функционал по аутентификации: class User extends lmbActiveRecord { [...] protected $is_logged_in = false; [...] function login($login, $password) { $hashed_password = User :: cryptPassword($password); $criteria = new lmbSQLFieldCriteria('login', $login); $criteria->addAnd(new lmbSQLFieldCriteria('hashed_password', $hashed_password)); if($user = lmbActiveRecord :: findFirst('User', array('criteria' => $criteria))) { $this->import($user); $this->setIsNew(false); $this->setIsLoggedIn(true); return true; } else { $this->setIsLoggedIn(false); return false; } } function logout() { $this->removeAll(); $this->is_logged_in = false; } } ==== Контроллер LoginController ==== За аутентификацию пользователей будет отвечать контроллер LoginController : request->hasPost()) { $user = $this->toolkit->getUser(); $this->useForm('login_form'); $this->setViewFormDatasource($this->request); $this->_validateLoginForm(); if(!$this->error_list->isValid()) return; $login = $this->request->get('login'); $password = $this->request->get('password'); if(!$user->login($login, $password)) { $this->flashError('Login or password incorrect!'); return; } $this->toolkit->getSession()->set('user_id', $user->getId()); $this->flashMessage('You were logged in!'); if($user->getIsAdmin()) $this->toolkit->redirect(array('controller' => 'admin')); else $this->toolkit->redirect('/'); } } protected function _validateLoginForm() { $this->validator->addRequiredRule('login'); $this->validator->addRequiredRule('password'); $this->validator->validate($this->request); } function doLogout() { $user = $this->toolkit->getUser(); $user->logout(); $this->toolkit->getSession()->remove('user_id'); $this->response->redirect('/'); } } ?> Большинство кода должны быть уже понятным, но некоторые моменты мы поясним. Методы **flashError($message)** и **flashMessage($message)** класса lmbController для вас новые. При помощи них можно передавать ошибки или другие сообщения в шаблон, наподобие того, как это происходит в Rails (конечно, если вы в курсе). Методы lmbController :: flashError($message) и lmbController :: flashMessage($message) на самом деле делегирует его в тулкит. Поэтому мы могли бы написать, например, $this->toolkit->flashError($message) и результат был бы тот же. Сообщения, выводимые через flashError() и flashMessage() хранятся в сесии и удаляются из нее в момент их отображения в шаблоне. Для отображения ошибок нам придется модифицировать шаблон page.html, о чем мы расскажем ниже. Логика работы LoginController :: doDisplay() должна быть понятной: если процедура аутентификации прошла успешно, мы должны сохранить идентификатор пользователя в сессии. Аналогично, если пользователь выходит из приложения (doLogout()), мы должны удалить его идентификатор из сессии. После аутентификации мы перебрасываем пользователя на стартовую страницу: * если он администратор - на /admin * если он не администратор - на главную страницу. Теперь создадим шаблон login/display.html, что позволит нам завершить процедуру аутентификации пользователя. После этого нам останется только один момент - отображать данные пользователя на страницах панели управления и выводить ссылку на выход из системы. Но для этого нам потребуются дополнительные знания, поэтому этот функционал мы добавим чуть позже. ==== Шаблон login/display.html ==== Файл shop/template/login/display.html:





==== Шаблон flash_box.html и вывод произвольных сообщений для пользователя ==== Создадим также шаблон flash_box.html, который будет использовать для вывода дополнительных ошибок, тех которые, например добавляются при помощи методов lmbController :: flashMessage() или lmbController :: flashError(). Файл shop/template/flash_box.html:
{$text}
{$text}
Для получения сообщений мы использовали тег [[limb3_2007_2:ru:usage:wact:tags:fetch_tags:fetch_tag|]] с lmbSessionMessageBoxFetcher в качестве источника данных. Каждое сообщение, полученное из lmbSessionMessageBoxFetcher содержит поля is_error, is_message и text. ==== Изменения в шаблоне page.html ==== Ввесем также минимальные изменения в page.html, где мы подключим новый шаблон flash_box.html: [...]

{$title}

[...]
Такие же изменения можно произвести с шаблоном admin_page.html, если потребуется. ===== Промежуточные результаты ===== Попробуем зайти на страницу /login нашего приложения и ввести любые данных в поля для ввода логина и пароля: {{{limb3_2007_2:ru:tutorials:shop:login_error.png|}}} Мы можете также попробовать ввести логин и пароль учетной записи, созданной ваши ранее. В этом случае вы увидите надпись "You were logged in!". ===== Далее ===== Теперь у нас есть функционал по аутентификации пользователей, но толку от него немного. Мы должны внедрить в наше приложение проверку прав доступа к панели управления, а также отобразить информацию для текущего пользователя. Итак, следующий шаг: [[step4-3|Шаг4.3 Проверка прав доступа]].