Yii PHP framework: создаём игровой сайт. Часть 3. Аутентификация.

Владимир | | PHP, Web разработка, Yii.

yii_game_site_auth

Приветствую всех! Эта статья – продолжение цикла о создании игрового сайта с использованием PHP фреймворка Yii.

Сегодня мы займемся ограничением доступа к административной части сайта. У вас может возникнуть вполне закономерный вопрос: «Почему именно ею? Ведь в предыдущих частях мы только создали базу данных, на сайте нет ни одной рабочей страницы.»

Всё правильно, но Yii имеет некоторые особенности. Код, который генерирует консольная утилита при создании контроллеров, автоматически использует встроенную библиотеку авторизации. Кроме того, в стандартном приложении, которое мы в прошлый раз создали командой yiic webapp есть все необходимые компоненты для аутентификации и авторизации пользователей.

От нас требуется только связать этот код с нашей базой данных. Т.е. кода нам нужно написать совсем немного.

Прежде всего, рассмотрим как вообще работает эта библиотека.

Основная её часть – это компонент реализующий интерфейс IWebUser. Доступ к нему можно получить так:

Yii::app()->user

Используя этот компонент (экземпляр класса СWebUser) можно проверить выполнил ли пользователь вход, получить его данные и т.п.

Для того, чтобы аутентифицировать пользователя мы должны определить класс, в котором будет находится код, проверяющий имя и пароль. Этот класс уже создан и находится он в файле protected\components\UserIdentity.php, но по-умолчанию имя и пароль записаны в обычном массиве, а нам нужно хранить их в базе данных.

Поэтому мы перепишем метод authenticate.

public function authenticate()
{
	//ищем пользователя по имени
	$record=Users::model()->findByAttributes(array('u_login'=>$this->username));
	//если пользователь найден и его пароль совпадает с введенным...
	if($record===null)
		$this->errorCode=self::ERROR_USERNAME_INVALID;
	else if($record->u_password!==md5($this->password))
		$this->errorCode=self::ERROR_PASSWORD_INVALID;
	else
	{
		//...сохраняем данные пользователя (имя и адрес фида)
		// (в принципе, адрес фида можно не сохранять, тогда при импорте игр
		// нужно будет выполнить дополнительный запрос)
		$this->_id=$record->u_id;
		$this->setState('name', $record->u_name);
		$this->setState('xml', $record->u_xml);
		$this->errorCode=self::ERROR_NONE;
	}
	return !$this->errorCode;
}

За основу я взял код из примера на официальном сайте, но немного его изменил.

Прежде всего, обратите внимание на строку 4. В ней мы ищем пользователя в базе данных. При этом используется класс модели Users, которую мы чуть позже создадим. Метод findByAttributes предназначен для поиска по заданным полям таблицы в БД. В качестве параметра этому методу мы передаем массив, в котором ключ элемента (u_login) соответствует названию поля, а его значение – значению, которое мы хотим найти.

Затем мы проверяем имя и пароль и, если проверка пройдена, сохраняем данные пользователя. Я решил сохранить имя пользователя и адрес его xml фида.

Теперь мы можем легко получить эти данные. Например,

Yii::app()->user->xml

Если бы мы их не сохранили, то пришлось бы выполнять запрос к БД.

Создадим модель Users.

Прежде всего, укажем параметры подключения к БД в файле конфигурации protected\config\main.php.

'db'=>array(
	'connectionString'=>'mysql:host=localhost;dbname=имя_бд',
	'username'=>'имя_пользователя',
	'password'=>'пароль',
	'charset'=>'utf8',
),

И выполняем в консоли две команды.

>> yiic shell
>> model Users ygs_users

После этого у нас появится файл protected\models\Users.php.

Тут же мы можем создать стандартный код для выполнения CRUD операций

>> crud Users

В результате появятся контроллер (protected\controllers\UsersController.php) и папка с файлами представлений (protected\views\users\). Сейчас мы их трогать не будем, они понадобятся в дальнейшем, когда мы будем заниматься админкой сайта.

А сейчас создадим ещё один контроллер.

>> controller Dashboard

Это не обязательно, но я не хочу чтобы форма входа относилась к контроллеру SiteController, который был создан по-умолчанию.

Перенесем форму входа в контроллер Dashboard, и заодно разберем каким образом выполняется аутентификация и авторизация пользователей.

Сама форма находится в файле protected\views\site\login.php. Поэтому просто перемещаем его в папку protected\views\dashboard\.

Настраиваем контроллер Dashboard.

Добавляем метод (на самом деле вручную писать код не нужно, можно просто скопировать метод из файла SiteController.php)

public function filters()
{
	return array(
		'accessControl', // perform access control for CRUD operations
	);
}

Теперь перед обращением к любому из методов контроллера фреймворк будет проверять имеет ли посетитель достаточные права.

Добавляем метод

public function accessRules()
{
	return array(
		array('allow',
			'actions'=>array('login','logout'),
			'users'=>array('*'),
		),
		array('allow',
			'actions'=>array('index'),
			'users'=>array('admin'),
		),
		array('deny',  // deny all users
			'users'=>array('*'),
		),
	);
}

В нём находится массив с правилами, которые указывают, что общедоступными являются только методы actionLogin() и actionLogout(). Первый показывает форму входа, второй – выполняет выход из системы.

Копируем и эти два метода

/**
 * Формирует страницу с формой входа
 */
public function actionLogin()
{
	$form=new LoginForm;
	// collect user input data
	if(isset($_POST['LoginForm']))
	{
		$form->attributes=$_POST['LoginForm'];
		// validate user input and redirect to previous page if valid
		if($form->validate())
			$this->redirect(Yii::app()->user->returnUrl);
	}
	// display the login form
	$this->render('login',array('form'=>$form));
}

/**
 * Выход из панели управления и переход на главную страницу.
 */
public function actionLogout()
{
	Yii::app()->user->logout();
	$this->redirect(Yii::app()->homeUrl);
}

С методом actionLogout(), надеюсь, всё понятно. Сначала завершаем сессию (метод logout()), затем отправляем редирект на домашнюю страницу.

Метод actionLogin() немного сложнее. Здесь мы создаём объект типа LoginForm. Класс LoginForm создан автоматически и находится в файле (protected\models\LoginForm.php).

Этот класс содержит метод authenticate, который вызывается для проверки поля с паролем. И именно в этом методе создаётся объект типа UserIdentity, который содержит нашу логику проверки имени и пароля. В этом же методе находится вызов Yii::app()->user->login(…).

Подводя итог, запишем порядок аутентификации.

1) Посетитель вводит имя / пароль и нажимает кнопку «Войти».

2) Фреймворк создает DashboardController и вызывает его метод actionLogin.

3) В методе actionLogin создается экземпляр класса LoginForm и вызывается его метод validate.

4) Фреймворк перебирает все правила, указанные в массиве, который возвращает метод rules. Одно из этих правил выглядит так
array('password', 'authenticate')
Это означает, что должен быть вызван метод LoginForm::authenticate(…).

5) LoginForm:: authenticate(…) создаёт экземпляр класса UserIdentity и вызывает его метод authenticate (в котором находится наша логика проверки имени и пароля).

6) Если все проверки прошли успешно, вызывается Yii::app()->user->login(…). Аутентификация завершена.

На первый взгляд, выглядит немного запутанно, но это потому, что используется несколько библиотек фреймворка одновременно. В частности, используется библиотека для проверки данных форм.

Тут я хочу отметить одно из отличий Yii от CodeIgniter. В состав Yii входит довольно много библиотек, и генератор кода их активно использует. Поэтому если библиотеки вас устраивают, вы выигрываете, т.к. значительная часть кода будет написана за вас. С другой стороны, если вы хотите использовать другую библиотеку, то эту часть кода придется переписать.

В следующей части мы рассмотрим работу с жанрами игр.

Все разделы цикла.

  1. Yii PHP framework: создаём игровой сайт. Часть 1. Постановка задачи.
  2. Yii PHP framework: создаём игровой сайт. Часть 2. База данных и установка фреймворка.
  3. Yii PHP framework: создаём игровой сайт. Часть 3. Аутентификация.
  4. Yii PHP framework: создаём игровой сайт. Часть 4. Работа с жанрами игр.
  5. Yii PHP framework: создаём игровой сайт. Часть 5. Импорт игр.
  6. Yii PHP framework: создаём игровой сайт. Часть 6. Формируем страницы игр и жанров.
  7. Yii PHP framework: создаём игровой сайт. Часть 7. Работа с JavaScript и страницы игр.
  8. Yii PHP framework: создаём игровой сайт. Часть 8. Создаём виджеты.
  9. Yii PHP framework: создаём игровой сайт. Часть 9. Поиск ошибок.
  10. Yii PHP framework: создаём игровой сайт. Часть 10. Панель управления.
  11. Yii PHP framework: создаём игровой сайт. Часть 11. Человекопонятные URL.
  12. Архив с исходниками

Постовой

Рекомендуем: абонентское кадровое обслуживание ООО и ИП. От профессионалов

  • bersy

    т.к намечается цикл статей, можете указывать теги, скажем Yii или хотя бы более общий framework, что бы в последствии облегчить навигацию? просто категории PHP, Web разработка у вас довольно объемные — можно затеряться 🙂

    благодарю!

    • Я собираюсь добавить в конец каждой части список ссылок на все остальные. Но идея с тегом Yii мне тоже нравится 🙂

  • bersy

    т.к намечается цикл статей, можете указывать теги, скажем Yii или хотя бы более общий framework, что бы в последствии облегчить навигацию? просто категории PHP, Web разработка у вас довольно объемные — можно затеряться 🙂

    благодарю!

    • Я собираюсь добавить в конец каждой части список ссылок на все остальные. Но идея с тегом Yii мне тоже нравится 🙂

  • Sam

    «Сейчас мы их трогать не будет» → «будем»
    «Теперь перед обращение» → «обращением»

    Ссылки лучше сразу давать на русскую документацию: http://yiiframework.ru/doc/guide/topics.auth.

    Стоит также отметить, что есть возможность более гибкой настройки прав через RBAC, хотя в этом случае она не понадобится.

    • Спасибо за исправления!

      Ссылки на русскую документацию — это правильно, но я пользовался всё время английским и он для меня привычнее.
      Я, наверное, сделаю отдельный анонс русской документации.

      RBAC. Учту замечание. Просто очень сложно выбрать о чем рассказывать, а что пропустить. С библиотекой для работы с БД ещё сложнее. Она объемная и запросы можно сформировать несколькими способами. В общем, посмотрим как получится, в любом случае уже есть переведённая документация 😉

      • Попытался собрать всё из того, что упущено в статье, но есть в коде:

        В конфиг main.php добавить после  'allowAutoLogin'=>true, URL для отправки формы
        'loginUrl'=>array('dashboard/login'),

        в classе UserIdentity extends CUserIdentity объявить переменную $_id
        private $_id;

        в файле protected/views/layouts/main.php исправить site/login на dashboard/login (аналогично с layout)

  • Sam

    «Сейчас мы их трогать не будет» ? «будем»
    «Теперь перед обращение» ? «обращением»

    Ссылки лучше сразу давать на русскую документацию: http://yiiframework.ru/doc/guide/topics.auth.

    Стоит также отметить, что есть возможность более гибкой настройки прав через RBAC, хотя в этом случае она не понадобится.

    • Спасибо за исправления!

      Ссылки на русскую документацию — это правильно, но я пользовался всё время английским и он для меня привычнее.
      Я, наверное, сделаю отдельный анонс русской документации.

      RBAC. Учту замечание. Просто очень сложно выбрать о чем рассказывать, а что пропустить. С библиотекой для работы с БД ещё сложнее. Она объемная и запросы можно сформировать несколькими способами. В общем, посмотрим как получится, в любом случае уже есть переведённая документация 😉

  • Спасибо за детальное описание, все понятно излагаете! Жду следующего поста по этой теме!

  • Спасибо за детальное описание, все понятно излагаете! Жду следующего поста по этой теме!

  • alex

    ну и в дополнении можно сказать, что при использовании filters() и accessRules() ограничения будут действовать только на контроллер, в котором они определены.

    Поэтому, чтобы не плодить код, просто расширяем его.

  • alex

    ну и в дополнении можно сказать, что при использовании filters() и accessRules() ограничения будут действовать только на контроллер, в котором они определены.

    Поэтому, чтобы не плодить код, просто расширяем его.

  • будут действовать только на контроллер, в котором они определены

    Безусловно, ведь в accessRules мы перечисляем названия методов контроллера (без приставки action), если бы была возможность ставить правила для методов другого контроллера, то запутаться было бы очень легко 😉

  • будут действовать только на контроллер, в котором они определены

    Безусловно, ведь в accessRules мы перечисляем названия методов контроллера (без приставки action), если бы была возможность ставить правила для методов другого контроллера, то запутаться было бы очень легко 😉

  • Максим

    У меня возникают проблемы с выполнением команд:

    >> yiic shell
    >> model Users ygs_users

    а именно:

    >> model Users ygs_users
    Warning: the table 'ygs_users' does not exist in the database.
    generate models/Users.php
    generate fixtures/ygs_users.php
    generate unit/UsersTest.php
    The following model classes are successfully generated:
    Users
    If you have a 'db' database connection, you can test these models now with:
    $model=Users::model()->find();
    print_r($model);
    >> crud Users
    Error: Table "ygs_users" does not have a primary key.

    Для создания таблиц использовал 'dump.sql' из архива.
    P.S.Использую Yii v1.1.0 на Денвере

    • Максим

      Удалил созданные файлы, попробовал еще раз — получилось.. чудеса:)

      • Действительно чудеса 🙂

        Обычно такое сообщение появляется если указанной таблицы нет в БД.

  • Максим

    У меня возникают проблемы с выполнением команд:

    >> yiic shell
    >> model Users ygs_users

    а именно:

    >> model Users ygs_users
    Warning: the table 'ygs_users' does not exist in the database.
    generate models/Users.php
    generate fixtures/ygs_users.php
    generate unit/UsersTest.php
    The following model classes are successfully generated:
    Users
    If you have a 'db' database connection, you can test these models now with:
    $model=Users::model()->find();
    print_r($model);
    >> crud Users
    Error: Table "ygs_users" does not have a primary key.

    Для создания таблиц использовал 'dump.sql' из архива.
    P.S.Использую Yii v1.1.0 на Денвере

    • Максим

      Удалил созданные файлы, попробовал еще раз — получилось.. чудеса:)

      • Действительно чудеса 🙂

        Обычно такое сообщение появляется если указанной таблицы нет в БД.

  • Одиночка Айс

    У меня не получается использовать контроллер Dashboard (в моем случае admin_panel). Yii ругается на то, что SiteController не может найти запрашиваемый вид login:

    SiteController cannot find the requested view «login».

    Я так понимаю, что он теряет управление, Admin_panelController еще не может вступить в права управления

    • Проверьте файл protected/config/main.php
      в нём должны быть такие настройки
      'user'=>array(
      'allowAutoLogin'=>true,
      'loginUrl'=>array('dashboard/login'),
      ),
      Кроме того, в папке protected/views/dashboard/ должен быть файл login.php.
      И метод actionLogin в файле protected/controllers/DashboardController.php.

  • Одиночка Айс

    У меня не получается использовать контроллер Dashboard (в моем случае admin_panel). Yii ругается на то, что SiteController не может найти запрашиваемый вид login:

    SiteController cannot find the requested view «login».

    Я так понимаю, что он теряет управление, Admin_panelController еще не может вступить в права управления

    • Проверьте файл protected/config/main.php
      в нём должны быть такие настройки
      'user'=>array(
      'allowAutoLogin'=>true,
      'loginUrl'=>array('dashboard/login'),
      ),
      Кроме того, в папке protected/views/dashboard/ должен быть файл login.php.
      И метод actionLogin в файле protected/controllers/DashboardController.php.

  • ditex

    стабильно выдаёт


    Fatal error: Call to a member function getDb() on a non-object in C:WebServers
    homelocalhostwwwframeworkclicommandsshellModelCommand.php on line 260

    В чём может быть проблема?

    • Эта ошибка вылезает когда вы yiic запускаете?
      Пока в голову приходят два варианта:
      1) неправильные параметры подключения к базе (первый листинг в этой статье).
      2) если у вас установлена vista или семёрка, доступ на запись на диск C может быть ограничен, можно попробовать запустить консоль от имени администратора.

  • ditex

    стабильно выдаёт


    Fatal error: Call to a member function getDb() on a non-object in C:\WebServers\
    home\localhost\www\framework\cli\commands\shell\ModelCommand.php on line 260

    В чём может быть проблема?

    • Эта ошибка вылезает когда вы yiic запускаете?
      Пока в голову приходят два варианта:
      1) неправильные параметры подключения к базе (первый листинг в этой статье).
      2) если у вас установлена vista или семёрка, доступ на запись на диск C может быть ограничен, можно попробовать запустить консоль от имени администратора.

  • ditex

    Ввожу yiic shell cd.. и всё нормально, но после ввода model Users ygs_users выдаёт эту ошибку. Перепроверил имя БД и логин, пароль — всё верно. Из под Админа в семёрке запускаю.

    ЗЫ. Ещё одна строчка ошибки (после line 260), в первый раз пропустилась почему-то

  • ditex

    Ввожу yiic shell cd.. и всё нормально, но после ввода model Users ygs_users выдаёт эту ошибку. Перепроверил имя БД и логин, пароль — всё верно. Из под Админа в семёрке запускаю.

    ЗЫ. Ещё одна строчка ошибки (после line 260), в первый раз пропустилась почему-то

  • ditex

    Местный скрипт режет строку 🙂 Попробую так:
    script language=JavaScript src='/denwer/errors/phperror_js.php'></script

    • Попробуйте вместо денвера использовать Wampserver. Его можно установить в какую-нибудь другую папку, главное не запускать одновременно с денвером.

      Создание файлов может блокировать система контроля пользователей (она выводит дополнительные запросы, например, при запуске инсталляторов), причём она работает даже если вы работаете как администратор.

      • ditex

        Спасибо помогло. Но теперь имею проблему, как у Максима (несколько постов выше). не могли бы вы полностью выложить полный текст функции authenticate(). Складывается впечатление, что я делаю что-то не так.

        PS. Никогда с фреймворками не работал — вот обучаюсь 🙂

        • Вы можете скачать архив с сайтом целиком (ссылка в конце статьи). Там будет и метод authenticate 🙂
          У Максима проблема вроде решилась удалением ранее созданных файлов.

  • ditex

    Местный скрипт режет строку 🙂 Попробую так:
    script language=JavaScript src='/denwer/errors/phperror_js.php'></script

    • Попробуйте вместо денвера использовать Wampserver. Его можно установить в какую-нибудь другую папку, главное не запускать одновременно с денвером.

      Создание файлов может блокировать система контроля пользователей (она выводит дополнительные запросы, например, при запуске инсталляторов), причём она работает даже если вы работаете как администратор.

      • ditex

        Спасибо помогло. Но теперь имею проблему, как у Максима (несколько постов выше). не могли бы вы полностью выложить полный текст функции authenticate(). Складывается впечатление, что я делаю что-то не так.

        PS. Никогда с фреймворками не работал — вот обучаюсь 🙂

        • Вы можете скачать архив с сайтом целиком (ссылка в конце статьи). Там будет и метод authenticate 🙂
          У Максима проблема вроде решилась удалением ранее созданных файлов.

  • Максим

    У Максима проблема вроде решилась удалением ранее созданных файлов.

    Да, именно так.

  • Максим

    У Максима проблема вроде решилась удалением ранее созданных файлов.

    Да, именно так.

  • Евгений

    Не получается настроить accessRules, в контроллере прописываю фильтры, но они не работают. Пишу в фильтр users запрет на действие не авторизованным пользователям, а на деле доступ остается открытым…В чем дело не пойму, вот код если поможет:

    public function filters()
    {
    return array(
    'accessControl',
    );
    }

    public function accessRules()
    {
    return array(
    array('allow', // allow all users to perform 'index' and 'view' actions
    'actions'=>array('password'),
    'users'=>array('@'),
    ),
    );
    }

    Это все что я добавил в контроллер, может чего нужна в конфиг прописать?!

    • Чтобы что-то сказать о фильтре, нужен и его код.

      Правила доступа. Попробуйте добавить в массив ещё одно правило (оно должно идти последним)
      array('deny', // deny all users
      'users'=>array('*'),
      ),

  • Евгений

    Не получается настроить accessRules, в контроллере прописываю фильтры, но они не работают. Пишу в фильтр users запрет на действие не авторизованным пользователям, а на деле доступ остается открытым…В чем дело не пойму, вот код если поможет:

    public function filters()
    {
    return array(
    'accessControl',
    );
    }

    public function accessRules()
    {
    return array(
    array('allow', // allow all users to perform 'index' and 'view' actions
    'actions'=>array('password'),
    'users'=>array('@'),
    ),
    );
    }

    Это все что я добавил в контроллер, может чего нужна в конфиг прописать?!

    • Чтобы что-то сказать о фильтре, нужен и его код.

      Правила доступа. Попробуйте добавить в массив ещё одно правило (оно должно идти последним)
      array('deny', // deny all users
      'users'=>array('*'),
      ),

  • Евгений

    Минутку, разве accessControl не является стандартным фильтром, или его тоже надо писать?!

    • Прошу прощения, я невнимательно прочитал комментарий. Конечно accessControl вручную создавать не нужно.

      Просто я привык создавать приложение с помощью yiic и думал, что вы добавили новый метод и пытаетесь закрыть к нему доступ.

      Вообще проще сделать именно так.

      В конфиге правила доступа и настройки фильтров не указываем.

  • Евгений

    Минутку, разве accessControl не является стандартным фильтром, или его тоже надо писать?!

    • Прошу прощения, я невнимательно прочитал комментарий. Конечно accessControl вручную создавать не нужно.

      Просто я привык создавать приложение с помощью yiic и думал, что вы добавили новый метод и пытаетесь закрыть к нему доступ.

      Вообще проще сделать именно так.

      В конфиге правила доступа и настройки фильтров не указываем.

  • BerdArt

    Поэтому мы перепишем метод authenticate.

    'db'=>array(
    'connectionString'=>'mysql:host=localhost;dbname=имя_бд',
    'username'=>'имя_пользователя',
    'password'=>'пароль',
    'charset'=>'utf8',
    ),

    По-моему здесь Вы написано не тот код, что хотели

  • BerdArt

    Поэтому мы перепишем метод authenticate.

    'db'=>array(
    'connectionString'=>'mysql:host=localhost;dbname=имя_бд',
    'username'=>'имя_пользователя',
    'password'=>'пароль',
    'charset'=>'utf8',
    ),

    По-моему здесь Вы написано не тот код, что хотели

  • Алексей

    Не получается использовать контроллер Dashboard.
    Добавил настройки в main.php и методы filters, accessRules, actionLodin и actionLogout в DashboardController.php, но не получается.
    Выдает ошибку:
    SiteController cannot find the requested view «login».

    Как я понял, от SiteController полностью отказались, и больше мы его не используем?

    • Не получается использовать контроллер Dashboard.

      Это значит, что по адресу
      sitename.domen/index.php/dashboard/login
      вы не видите форму входа?

      Ошибка означает, что вы обращаетесь к SiteController и в нем отсутствует метод login.

      • Алексей

        По адресу sitename.domen/index.php?r=dashboard/login
        формы нет, а выдает это:
        Login
        Please fill out the following form with your login credentials:
        Fields with * are required.
        Fatal error: Call to a member function isAttributeRequired() on a non-object in I:homeYiiwwwframeworkwebhelpersCHtml.php on line 1109

        И что нужно сделать, что бы ссылался на dashboard/login, а не на site/login.

        • В конце этой статьи есть ссылка на архив с исходниками этого примера. Вы не пробовали его запустить?

          Судя по описанию ошибки, возникает чувство, что вы что-то напутали в настройках, но точно сказать не могу.

        • юрий

          вместо кода приведенного в статье
          public function actionLogin()
          {

          }

          нужно использовать код из SiteController.php

          public function actionLogin()
          {
          $model=new LoginForm;

          // if it is ajax validation request
          if(isset($_POST['ajax']) && $_POST['ajax']==='login-form')
          {
          echo CActiveForm::validate($model);
          Yii::app()->end();
          }

          // collect user input data
          if(isset($_POST['LoginForm']))
          {
          $model->attributes=$_POST['LoginForm'];
          // validate user input and redirect to the previous page if valid
          if($model->validate() && $model->login())
          $this->redirect(Yii::app()->user->returnUrl);
          }
          // display the login form
          $this->render('login',array('model'=>$model));
          }

          видать вышла новая версия yii и получилась накладка

        • Вы правы, в последних версиях этот код изменился. В частности, добавлена обработка ajax запросов.

    • Проверьте в настройках main.php параметр
      'loginUrl'=>array('dashboard/login'),

      • Алексей

        Это было.

        • Alexdotcom

          У меня такая же ошибка была — Юрий правду сказал:
          «вместо кода приведенного в статье public function actionLogin() {… }нужно использовать код из SiteController.php»

  • Алексей

    Не получается использовать контроллер Dashboard.
    Добавил настройки в main.php и методы filters, accessRules, actionLodin и actionLogout в DashboardController.php, но не получается.
    Выдает ошибку:
    SiteController cannot find the requested view «login».

    Как я понял, от SiteController полностью отказались, и больше мы его не используем?

    • Не получается использовать контроллер Dashboard.

      Это значит, что по адресу
      sitename.domen/index.php/dashboard/login
      вы не видите форму входа?

      Ошибка означает, что вы обращаетесь к SiteController и в нем отсутствует метод login.

      • Алексей

        По адресу sitename.domen/index.php?r=dashboard/login
        формы нет, а выдает это:
        Login
        Please fill out the following form with your login credentials:
        Fields with * are required.
        Fatal error: Call to a member function isAttributeRequired() on a non-object in I:\home\Yii\www\framework\web\helpers\CHtml.php on line 1109

        И что нужно сделать, что бы ссылался на dashboard/login, а не на site/login.

        • В конце этой статьи есть ссылка на архив с исходниками этого примера. Вы не пробовали его запустить?

          Судя по описанию ошибки, возникает чувство, что вы что-то напутали в настройках, но точно сказать не могу.

    • Проверьте в настройках main.php параметр
      'loginUrl'=>array('dashboard/login'),

      • Алексей

        Это было.

  • cedage

    код из UserIdentity:

    findByAttributes(array('login'=>$this->username));
    echo «»;
    print_r($record);
    echo «»;
    if($record===null){
    $this->errorCode=self::ERROR_USERNAME_INVALID;
    }else if($record->password!==md5($this->password)){
    $this->errorCode=self::ERROR_PASSWORD_INVALID;
    }else{
    $this->_id=$record->id;
    $this->setState('login', $record->username);
    $this->errorCode=self::ERROR_NONE;
    }
    return !$this->errorCode;
    }

    public function getId(){
    return $this->_id;
    }

    }

    после выборки из базы массив $record выглядит вот так

    Users Object
    (
    [_md:private] => CActiveRecordMetaData Object
    (
    [tableSchema] => CMysqlTableSchema Object
    (
    [schemaName] =>
    [name] => users
    [rawName] => `users`
    [primaryKey] => id
    [sequenceName] =>
    [foreignKeys] => Array
    (
    )

    [columns] => Array
    (
    [id] => CMysqlColumnSchema Object
    (
    [name] => id
    [rawName] => `id`
    [allowNull] =>
    [dbType] => int(11)
    [type] => integer
    [defaultValue] =>
    [size] => 11
    [precision] => 11
    [scale] =>
    [isPrimaryKey] => 1
    [isForeignKey] =>
    [_e:private] =>
    [_m:private] =>
    )

    [login] => CMysqlColumnSchema Object
    (
    [name] => login
    [rawName] => `login`
    [allowNull] =>
    [dbType] => varchar(50)
    [type] => string
    [defaultValue] =>
    [size] => 50
    [precision] => 50
    [scale] =>
    [isPrimaryKey] =>
    [isForeignKey] =>
    [_e:private] =>
    [_m:private] =>
    )

    [password] => CMysqlColumnSchema Object
    (
    [name] => password
    [rawName] => `password`
    [allowNull] =>
    [dbType] => char(32)
    [type] => string
    [defaultValue] =>
    [size] => 32
    [precision] => 32
    [scale] =>
    [isPrimaryKey] =>
    [isForeignKey] =>
    [_e:private] =>
    [_m:private] =>
    )

    )

    [_e:private] =>
    [_m:private] =>
    )

    [columns] => Array
    (
    [id] => CMysqlColumnSchema Object
    (
    [name] => id
    [rawName] => `id`
    [allowNull] =>
    [dbType] => int(11)
    [type] => integer
    [defaultValue] =>
    [size] => 11
    [precision] => 11
    [scale] =>
    [isPrimaryKey] => 1
    [isForeignKey] =>
    [_e:private] =>
    [_m:private] =>
    )

    [login] => CMysqlColumnSchema Object
    (
    [name] => login
    [rawName] => `login`
    [allowNull] =>

    мне не совсем понятно что значит стока $this->setState('login', $record->username);
    если $record->username несуществует

  • cedage

    код из UserIdentity:

    findByAttributes(array('login'=>$this->username));
    echo «»;
    print_r($record);
    echo «»;
    if($record===null){
    $this->errorCode=self::ERROR_USERNAME_INVALID;
    }else if($record->password!==md5($this->password)){
    $this->errorCode=self::ERROR_PASSWORD_INVALID;
    }else{
    $this->_id=$record->id;
    $this->setState('login', $record->username);
    $this->errorCode=self::ERROR_NONE;
    }
    return !$this->errorCode;
    }

    public function getId(){
    return $this->_id;
    }

    }

    после выборки из базы массив $record выглядит вот так

    Users Object
    (
    [_md:private] => CActiveRecordMetaData Object
    (
    [tableSchema] => CMysqlTableSchema Object
    (
    [schemaName] =>
    [name] => users
    [rawName] => `users`
    [primaryKey] => id
    [sequenceName] =>
    [foreignKeys] => Array
    (
    )

    [columns] => Array
    (
    [id] => CMysqlColumnSchema Object
    (
    [name] => id
    [rawName] => `id`
    [allowNull] =>
    [dbType] => int(11)
    [type] => integer
    [defaultValue] =>
    [size] => 11
    [precision] => 11
    [scale] =>
    [isPrimaryKey] => 1
    [isForeignKey] =>
    [_e:private] =>
    [_m:private] =>
    )

    [login] => CMysqlColumnSchema Object
    (
    [name] => login
    [rawName] => `login`
    [allowNull] =>
    [dbType] => varchar(50)
    [type] => string
    [defaultValue] =>
    [size] => 50
    [precision] => 50
    [scale] =>
    [isPrimaryKey] =>
    [isForeignKey] =>
    [_e:private] =>
    [_m:private] =>
    )

    [password] => CMysqlColumnSchema Object
    (
    [name] => password
    [rawName] => `password`
    [allowNull] =>
    [dbType] => char(32)
    [type] => string
    [defaultValue] =>
    [size] => 32
    [precision] => 32
    [scale] =>
    [isPrimaryKey] =>
    [isForeignKey] =>
    [_e:private] =>
    [_m:private] =>
    )

    )

    [_e:private] =>
    [_m:private] =>
    )

    [columns] => Array
    (
    [id] => CMysqlColumnSchema Object
    (
    [name] => id
    [rawName] => `id`
    [allowNull] =>
    [dbType] => int(11)
    [type] => integer
    [defaultValue] =>
    [size] => 11
    [precision] => 11
    [scale] =>
    [isPrimaryKey] => 1
    [isForeignKey] =>
    [_e:private] =>
    [_m:private] =>
    )

    [login] => CMysqlColumnSchema Object
    (
    [name] => login
    [rawName] => `login`
    [allowNull] =>

    мне не совсем понятно что значит стока $this->setState('login', $record->username);
    если $record->username несуществует

  • cedage


    findByAttributes(array('login'=>$this->username));
    if($record===null){
    $this->errorCode=self::ERROR_USERNAME_INVALID;
    }else if($record->password!==md5($this->password)){
    $this->errorCode=self::ERROR_PASSWORD_INVALID;
    }else{
    $this->_id=$record->id;
    $this->setState('login', $record->username);
    $this->errorCode=self::ERROR_NONE;
    }
    return !$this->errorCode;
    }

    public function getId(){
    return $this->_id;
    }

    }

  • cedage

    сори за бардак — класс почему то не выкладывается ….

    • Посмотрите класс LoginForm, думаю, все станет ясно 😉
      Он выполняет чтение данных форму и их проверку. В данном случае $this->username содержит имя пользователя.

      • cedage

        самый простой алгоритм — передаем данные из формы в контроллер, далее отправляем их в модель проверяем в базе если есть такое имя и пароль — создаем сессию и редирект на другую страницу.

        в данном случае совсем не понятно как работает авторизация, в AuthController передаем данные из формы, проверяем если они есть if(isset($_POST['LoginForm'])), передаем в ранне созданный экземпляр класса LoginForm наш массив с данными т.е. $form->attributes=$_POST['LoginForm']

        открываем LoginForm — видим следующе:
        1. правила которые проверяют наш $_POST, проверили — все отлично идем дальше
        2. натыкаемся на ф-ю authenticate, вызывается она в правилах после удовлетрорительной валидации, тут же создаем экземпляр класса UserIdentity и передаем ему $_POST данные т.е. логин и пароль в методе authenticate есть строка $record=Users::model()->findByAttributes(array(«login»=>$this->username)); т.е. выборка из базы, после чего происходит следующее $this->setState('login', $record->username); откуда взялся $record->username если вывести print_r($record); то ключа гsername там нету, нету даже никакого упоминания о данных которые мы вытянули из базы, массив см.выше. + к тому же выводит ошибку Не определено свойство «Users.username».

      • cedage

        полсе того как выполнили Yii::app()->user->login()
        данные содержащиеся в Yii::app()->user никак не меняются, как нужно понимать когда создавать сессию ?

        • Я понимаю, что система выглядит запутанно, но это только для того, чтобы сократить количество действий с вашей стороны.

          В данном случае класс LoginForm является моделью с атрибутами
          public $username;
          public $password;
          public $rememberMe;
          и методом
          public function authenticate(…)
          В этом методе создается объект
          UserIdentity($this->username,$this->password)

          Присваивание значений свойствам происходит вызовом
          $form->attributes=$_POST['LoginForm'];
          $_POST['LoginForm'] — содержит все поля формы.
          Присвоение конкретных значений свойствам выполняется библиотекой Yii.

        • Сессия создается с помощью
          Yii::app()->user->login($identity,$duration);

  • cedage


    findByAttributes(array('login'=>$this->username));
    if($record===null){
    $this->errorCode=self::ERROR_USERNAME_INVALID;
    }else if($record->password!==md5($this->password)){
    $this->errorCode=self::ERROR_PASSWORD_INVALID;
    }else{
    $this->_id=$record->id;
    $this->setState('login', $record->username);
    $this->errorCode=self::ERROR_NONE;
    }
    return !$this->errorCode;
    }

    public function getId(){
    return $this->_id;
    }

    }

  • cedage

    сори за бардак — класс почему то не выкладывается ….

    • Посмотрите класс LoginForm, думаю, все станет ясно 😉
      Он выполняет чтение данных форму и их проверку. В данном случае $this->username содержит имя пользователя.

      • cedage

        самый простой алгоритм — передаем данные из формы в контроллер, далее отправляем их в модель проверяем в базе если есть такое имя и пароль — создаем сессию и редирект на другую страницу.

        в данном случае совсем не понятно как работает авторизация, в AuthController передаем данные из формы, проверяем если они есть if(isset($_POST['LoginForm'])), передаем в ранне созданный экземпляр класса LoginForm наш массив с данными т.е. $form->attributes=$_POST['LoginForm']

        открываем LoginForm — видим следующе:
        1. правила которые проверяют наш $_POST, проверили — все отлично идем дальше
        2. натыкаемся на ф-ю authenticate, вызывается она в правилах после удовлетрорительной валидации, тут же создаем экземпляр класса UserIdentity и передаем ему $_POST данные т.е. логин и пароль в методе authenticate есть строка $record=Users::model()->findByAttributes(array(«login»=>$this->username)); т.е. выборка из базы, после чего происходит следующее $this->setState('login', $record->username); откуда взялся $record->username если вывести print_r($record); то ключа гsername там нету, нету даже никакого упоминания о данных которые мы вытянули из базы, массив см.выше. + к тому же выводит ошибку Не определено свойство «Users.username».

      • cedage

        полсе того как выполнили Yii::app()->user->login()
        данные содержащиеся в Yii::app()->user никак не меняются, как нужно понимать когда создавать сессию ?

        • Я понимаю, что система выглядит запутанно, но это только для того, чтобы сократить количество действий с вашей стороны.

          В данном случае класс LoginForm является моделью с атрибутами
          public $username;
          public $password;
          public $rememberMe;
          и методом
          public function authenticate(…)
          В этом методе создается объект
          UserIdentity($this->username,$this->password)

          Присваивание значений свойствам происходит вызовом
          $form->attributes=$_POST['LoginForm'];
          $_POST['LoginForm'] — содержит все поля формы.
          Присвоение конкретных значений свойствам выполняется библиотекой Yii.

        • Сессия создается с помощью
          Yii::app()->user->login($identity,$duration);

  • cedage

    в классе UserIdentity есть строка $this->setState('login', $record->username); выдаем ошибку 'Не определено свойство «Users.username».' — почему так ?

    • Судя по всему, у вас в таблице Users нет поля с названием username.
      Если вы получали $record таким образом

      $record=Users::model()->findByAttributes(array('u_login'=>$this->username));

      то сохранить данные в сессии можно так

      $this->setState('xml', $record->u_xml);

      u_xml — имя поля в БД.
      Вообще, лучше посмотреть имена полей в файле модели (models/Users.php), там в начале будет примерно такой комментарий.

      * The followings are the available columns in table 'ygs_users':
      * @var integer $u_id
      * @var string $u_name
      * @var string $u_login
      * @var string $u_password
      * @var string $u_xml

      • cedage

        спасибо работает.
        Много вещей просто в голове не укладывается, на Yii перехожу с cakePhp, в котором выборку из базы можно было посмотреть через pr($results); и можно увидеть все наши поля в виде массива, в Yii же непонятно где в массиве $record находиться значение username, и работает все правильно и такое ощущение что все это с воздуха берется …

        вот еще один момент не понятен, вообщем добавил в контроллер
        public function filters(){
        return array(
        «accessControl»
        );
        }

        и

        public function accessRules(){
        return array(
        array(«deny»,
        «actions» => array(«index2»),
        «users» => array(«admin», «root»)
        )
        );
        }

        ну сообственно сам индекс 2

        public function actionIndex2(){
        echo «index-2»;
        $this->render(«index»);
        }

        кроме второго у меня таких индексов 4, блокируются все, ни к одному нет доступа.
        откуда подставляются значения в массив users в accessRules ?
        после успешного входа переменная Yii::app()->user->login возвращает имя под которым мы зашли, это либо admin либо root,
        а вот print_r(Yii::app()->user) никак не меняется, до и после логина т.е. постоянно мне выводит вот такой массив
        CWebUser Object
        (
        [allowAutoLogin] =>
        [guestName] => Guest
        [loginUrl] => Array
        (
        [0] => /site/login
        )

        [identityCookie] =>
        [autoRenewCookie] =>
        [_keyPrefix:private] => 4a4a78b621150e3f20e5d32f4ba9a0e2
        [_access:private] => Array
        (
        )

        [behaviors] => Array
        (
        )

        [_initialized:private] => 1
        [_e:private] =>
        [_m:private] =>
        )

        • Очень хорошо эта тема описана здесь.
          Код, который вы привели, не содержит правила allow, только deny (…array(«deny»…), поэтому и доступа нет.

      • то сохранить данные в сессии можно так
        $this->setState('xml', $record->u_xml);

        Следует иметь ввиду, что в этом случае (при импорте используется значение из сессии), обновление данных в базе (модель user, свойство u_xml) не приведет к обновлению значения xml в сессии до повторной авторизации.
        Т.е. несмотря на изменение URL, данные будут загружаться со старого адреса.

        Аналогичная ситуация может возникнуть в случае хранения в сессии, например, прав пользователя. Т.е. «по базе» пользователь может уже не иметь прав (к примеру, мы его заблокировали), а «по сессии» — всё ещё имеет(активен), если авторизация была произведена до блокировки.

        ps. Прошу извинить за «понятность» фраз =)

  • cedage

    в классе UserIdentity есть строка $this->setState('login', $record->username); выдаем ошибку 'Не определено свойство «Users.username».' — почему так ?

    • Судя по всему, у вас в таблице Users нет поля с названием username.
      Если вы получали $record таким образом

      $record=Users::model()->findByAttributes(array('u_login'=>$this->username));

      то сохранить данные в сессии можно так

      $this->setState('xml', $record->u_xml);

      u_xml — имя поля в БД.
      Вообще, лучше посмотреть имена полей в файле модели (models/Users.php), там в начале будет примерно такой комментарий.

      * The followings are the available columns in table 'ygs_users':
      * @var integer $u_id
      * @var string $u_name
      * @var string $u_login
      * @var string $u_password
      * @var string $u_xml

      • cedage

        спасибо работает.
        Много вещей просто в голове не укладывается, на Yii перехожу с cakePhp, в котором выборку из базы можно было посмотреть через pr($results); и можно увидеть все наши поля в виде массива, в Yii же непонятно где в массиве $record находиться значение username, и работает все правильно и такое ощущение что все это с воздуха берется …

        вот еще один момент не понятен, вообщем добавил в контроллер
        public function filters(){
        return array(
        «accessControl»
        );
        }

        и

        public function accessRules(){
        return array(
        array(«deny»,
        «actions» => array(«index2»),
        «users» => array(«admin», «root»)
        )
        );
        }

        ну сообственно сам индекс 2

        public function actionIndex2(){
        echo «index-2»;
        $this->render(«index»);
        }

        кроме второго у меня таких индексов 4, блокируются все, ни к одному нет доступа.
        откуда подставляются значения в массив users в accessRules ?
        после успешного входа переменная Yii::app()->user->login возвращает имя под которым мы зашли, это либо admin либо root,
        а вот print_r(Yii::app()->user) никак не меняется, до и после логина т.е. постоянно мне выводит вот такой массив
        CWebUser Object
        (
        [allowAutoLogin] =>
        [guestName] => Guest
        [loginUrl] => Array
        (
        [0] => /site/login
        )

        [identityCookie] =>
        [autoRenewCookie] =>
        [_keyPrefix:private] => 4a4a78b621150e3f20e5d32f4ba9a0e2
        [_access:private] => Array
        (
        )

        [behaviors] => Array
        (
        )

        [_initialized:private] => 1
        [_e:private] =>
        [_m:private] =>
        )

        • Очень хорошо эта тема описана здесь.
          Код, который вы привели, не содержит правила allow, только deny (…array(«deny»…), поэтому и доступа нет.

  • cedage

    не в этом дело, добавил allow правило все так же
    public function accessRules(){
    return array(
    array(«allow»,
    «actions» => array(«index», «index2»),
    «users» => array(«admin»)
    ),
    array(«deny»,
    «actions» => array(«index», «index2»),
    «users» => array(«root»)
    )
    );
    }

    под админом нет доступа к index и index2, так же блокируются index3 и index4, тоже самое происходит если залогиниться под рутом

    изменил запись

    public function accessRules(){
    return array(
    array(«allow»,
    «actions» => array(«*»),
    «users» => array(«root»)
    ),
    array(«deny»,
    «actions» => array(«index3», «index2»),
    «users» => array(«admin»)
    )
    );
    }

    ничего не поменялось
    статью про авторизацию пречитал уже раз 20, подобные ситуации там не описываются…

  • cedage

    не в этом дело, добавил allow правило все так же
    public function accessRules(){
    return array(
    array(«allow»,
    «actions» => array(«index», «index2»),
    «users» => array(«admin»)
    ),
    array(«deny»,
    «actions» => array(«index», «index2»),
    «users» => array(«root»)
    )
    );
    }

    под админом нет доступа к index и index2, так же блокируются index3 и index4, тоже самое происходит если залогиниться под рутом

    изменил запись

    public function accessRules(){
    return array(
    array(«allow»,
    «actions» => array(«*»),
    «users» => array(«root»)
    ),
    array(«deny»,
    «actions» => array(«index3», «index2»),
    «users» => array(«admin»)
    )
    );
    }

    ничего не поменялось
    статью про авторизацию пречитал уже раз 20, подобные ситуации там не описываются…

  • cedage

    прошу прощения — все работает, запудрил себе моск, бывает ))
    спасибо!!!

    • Пожалуйста 🙂 Я сам периодически путаюсь в настройках 🙂

  • cedage

    прошу прощения — все работает, запудрил себе моск, бывает ))
    спасибо!!!

    • Пожалуйста 🙂 Я сам периодически путаюсь в настройках 🙂

  • Вы забыли про private $_id; перед public function authenticate(), т.к. версия 1.1.2 это сама не генерирует, а к этой статье исходников нет, и в статье нет об этом упоминания, поэтому если не добавить private $_id; то получаю ошибку
    «Property «UserIdentity._id» is not defined.»

  • Спасибо, учту.

  • Mazitov_lenar

    Здравствуйте, замечание:

    Добавьте Yii::app()->user->login($this) в Метод UserIdentity::authenticate()

  • Спасибо за замечание!
    Я все проверю.

  • Petr

    та же фигня…

  • Petr

    Почему в статье не написано, что нужно добавить еще и:
    'loginUrl'=>array('dashboard/login'),

  • Хочу уточнить, вы просто запускаете мой пример? Или что-то переделывали?

    Посмотрите в файле views/dashboard/login.php вызовы методов класса CHtml (начинаются с CHtml::…), возможно в каком-то из них допущена ошибка при передаче параметров.

  • Спасибо за замечание, пропустил.

    В архиве с исходниками эта строчка есть 😉

    • Предла

      • * Предлагаю, всё же добавить строчку в описание 🙂

  • Elabismo

     Учиться по вашим статьям намного приятнее чем по официальному руководству )