Yii PHP framework: создаём игровой сайт. Часть 10. Панель управления.

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

yii game site dashboard

Сегодня мы займёмся панелью управления сайта. Часть этой панели мы уже сделали.

В третьей части (Аутентификация) мы создали контроллер DashboardController и переместили в него методы actionLogin и actionLogout.

Теперь нам нужно добавить методы для управления играми, пользователями и жанрами. По большому счёту, нужные методы у нас есть, они были автоматически созданы утилитой yiic, но часть методов нужно переделывать. Например, методы управления жанрами нам вполне подходят, т.к. ничего кроме обычных CRUD операций с жанрами нам делать не нужно. Но при редактировании игр нам нужны дополнительные возможности.

Для каждой игры нужно показать полный перечень жанров с чекбоксами, чтобы пользователь мог его отредактировать. Кроме того, нужен список скриншотов, и возможность удаления неподходящих.

В первую очередь создадим отдельный макет для страниц админки.

Создаём файл (protected/views/layouts/dashboard.php). Я покажу только несколько его фрагментов

<!DOCTYPE html ...>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
...
<title><?php echo $this->pageTitle; ?></title>
</head>

<body>
<div id="page" class="container_12">

<div id="header" class="grid_12">
<div id="logo">Панель управления</div>
</div><!-- header -->

<div id="sidebar" class="grid_4">
<?php $this->widget('application.components.dashboardMenu'); ?>
</div><!-- sidebar -->

<div class="grid_8">
<div id="content">
<?php echo $content; ?>
<div class="clear"></div>
</div><!-- content -->
</div>

<div id="footer" class="grid_12">
...
</div><!-- footer -->

<div class="clear"></div>
</div><!-- page -->
</body>
</html>

Как видите, шаблон стандартный: шапка, меню слева, блок контента и хвостовик.

Обратите внимание на сайдбар. Он создан с помощью виджета application.components.dashboardMenu. Подробно создание виджетов мы рассматривали в восьмой части.

Этот виджет просто выводит список ссылок на основные страницы панели управления. Единственная особенность в том, что мы не показываем его на странице с формой входа. Для этого в методе run выполняется соответствующая проверка.

public function run() {
	//если мы находимся на странице с формой входа, то этот виджет не показываем
	$action = Yii::app()->controller->action->id;
	if ('login' === $action) {
		return;
	}
	
	$this->render('dashboardMenu');
}

Переходим к более сложной части – управлению играми.

В меню для игр предусмотрено два пункта: Управление (вызывается метод actionAdmin контроллера GamesController) и Импорт (метод actionImport контроллера GamesController).

Обратите внимание, у нас нет пункта «Создать игру». Дело в том, что все игры мы получаем из фида партнёрки. Т.е. за создание новых игр отвечает метод «Импорт» (его мы рассматривали в пятой части).

Метод actionAdmin выводит таблицу с общим перечнем игр и ссылками на действия Update и Delete. Сам метод создан утилитой yiic и мои изменения коснулись только количества столбцов в результирующей таблице. По-умолчанию, выводятся все поля, но от большинства из них в этой таблице нет никакой практической пользы.

Теперь рассмотрим каким образом выполняется изменение игр (метод actionUpdate).

public function actionUpdate()
{
	$cs = Yii::app()->clientScript;
	//подключаем редактор tinyMce для всех textarea
	$cs->registerScriptFile(Yii::app()->request->baseUrl.'/js/tiny_mce/tiny_mce_gzip.js', CClientScript::POS_END);
	$cs->registerScriptFile(Yii::app()->request->baseUrl.'/js/tiny_mce/init_gz.js' ,CClientScript::POS_END);
	$cs->registerScriptFile(Yii::app()->request->baseUrl.'/js/tiny_mce/init_editor.js' ,CClientScript::POS_END);
	//получаем данные игры из базы
	$model=$this->loadGames();
	//получаем список жанров
	$types = Types::model()->findAll();
	//если получены данные игры
	if(isset($_POST['Games']))
	{
		//записываем атрибуты
		$model->attributes=$_POST['Games'];
		//получаем список чекбоксов, соответствующих выбранным жанрам...
		$model->g_types=$_POST['types'];
		//... кодируем их и результат записываем в атрибут g_type
		$model->g_type = $this->_encodeTypes();
		//эта переменная запрещает изменение списка скриншотов игры
		//(т.к. для удаления скриншотов используется отдельная форма)
		$model->updateScreenshots = false;
		//сохраняем игру
		if($model->save())
			$this->redirect(array('update','id'=>$model->g_id));
	}
	//если получены данные из формы удаления скриншотов
	if (isset($_POST['screenshots'])) {
		//удаляем выбранные скриншоты
		foreach ($_POST['screenshots'] as $id) {
			$s = Screenshots::model()->findByPk($id);
			$s->delete();
		}
	}
	//показываем форму
	$this->render('update',array('model'=>$model, 'types'=>$types));
}

В первую очередь, подключаем редактор tinyMce. Для этого используется метод registerScriptFile и работу с ним мы рассматривали седьмой части, поэтому повторяться не буду.

Затем, получаем данные игры из базы (с помощью метода loadGames, строка 9).

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

На следующем этапе проверяем заходит ли пользователь первый раз на страницу или он отправил форму с обновлёнными данными игры.

Сохранение данных выполняется в два этапа:

1) проверяем какие чекбоксы выбраны и кодируем жанр игры (строки 18-20);

2) сохраняем изменённые данные (строки 25, 26), если сохранение прошло успешно, отправляем редирект на эту же страницу.

Обратите внимание, на атрибут updateScreenshots. Мы установили его значение равным false. Это означает, что при изменении игры скриншоты удаляться не будут. Проверка этого атрибута выполняется в методе afterSave модели Games.

Если пришли данные формы скриншотов, то удаляем выбранные (строки 29-35).

Рассмотрим представление views/games/update.php.

<?php
//страница "Редактирование игры"
//шаблон dashboard
$this->layout = 'dashboard';
?>
<h2>Редактирование: <?php echo $model->g_name; ?></h2>

<div class="actionBar">
[<?php echo CHtml::link('Просмотр',array('list')); ?>]
[<?php echo CHtml::link('Управление играми',array('admin')); ?>]
</div>

<?php echo $this->renderPartial('_form', array(
	'model'=>$model,
	'update'=>true,
	'types'=>$types
)); ?>

Обратите внимание, что здесь мы явно указываем какой макет нужно использовать (строка 3). А для создания формы используется представление views/games/_form.php, которому мы передаём данные выбранной игры и полный список жанров.

Приводить код всего представления _form.php я не будут, т.к. в нём много повторяющихся блоков. Остановимся на создании блока со списком жанров и формы со скриншотами.

<?php //Эта форма используется для обновления данных игры и удаления скриншотов ?>
<div class="yiiForm">

<p>
Обязательные поля отмеченны <span class="required">*</span>.
</p>

<?php echo CHtml::beginForm(); ?>

<?php echo CHtml::errorSummary($model); ?>

<div class="simple">
<?php echo CHtml::activeLabelEx($model,'g_rate'); ?>
<?php echo CHtml::activeTextField($model,'g_rate'); ?>
</div>

...

<?php echo CHtml::activeLabelEx($model,'g_state'); ?>
<?php echo CHtml::activeDropDownList($model, 'g_state'
		, array('0'=>'Опубликовано','1'=>'Черновик')); ?>
</div>
<div class="types_list">
<?php echo '<strong>Жанры</strong>:<br />'; ?>
<?php
$curTypes = $model->ygs_types;
$curT = array();
foreach ($curTypes as $type) {
	$curT[] = $type->t_id;
}
$allT = array();
foreach ($types as $type) {
	$allT[$type->t_id] = $type->t_name;
}
echo CHtml::checkBoxList('types',$curT,$allT, array('separator'=>''));
?>

...

<?php echo CHtml::endForm(); ?>

</div><!-- yiiForm -->

<?php //форма удаления скриншотов ?>

<div class="yiiForm">
<h2>Управление скриншотами</h2>
<?php echo CHtml::beginForm('', 'post', array('id'=>'screenshots_form')); ?>
<div class="simple">
<?php
$screenshots = $model->ygs_screenshots;
$gameScreenshots = array();
foreach ($screenshots as $screenshot) {
	$gameScreenshots[$screenshot->s_id] = CHtml::image($screenshot->s_thumbnail, $screenshot->s_game_id);
}
echo CHtml::checkBoxList('screenshots',array(),$gameScreenshots);
?>
</div>
<div class="action">
<?php echo CHtml::submitButton('Удалить отмеченные'); ?>
</div>
<?php echo CHtml::endForm(); ?>
</div><!-- yiiForm -->

Для вывода списка чекбоксов мы использовали метод CHtml::checkBoxList. Для этого метода нам нужно сформировать два массива: со значениями и с текстом соответствующих чекбоксов. Эти массивы формируются в циклах (строки 28-34).

Форма со списком скриншотов формируется точно также. Мы создаём список чекбоксов и около каждого вставляем картинку (метод CHtml::image).

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

На этом мы сегодня остановимся. Приводить код всех представлений, которые используются в админке, я не вижу смысла.

Если есть вопросы или замечания, пишите, обсудим 😉

P.S. Вы можете скачать архив с исходным кодом этого примера

Source

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

  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. Архив с исходниками

Интересно почитать

Источники бесперебойного питания от компании APC.

  • Как Вы считаете использовать
    $this->layout = 'dashboard';
    лучше в представлении или в контроллере ?
    Я просто всегда писал это в контроллер, теперь увидел Ваше решение и задумался…

    • На мой вгляд, логичнее в представлении, точнее не логичнее, а ближе к MVC архитектуре. Вообще, если в методе контроллера вызывается только одно представление, то разницы нет. Но если контроллер выбирает какое представление загружать (например, на основе полученных параметров), то удобнее вынести установку макета в представление.

      Кроме того, есть ещё один аргумент. Мне кажется, разбираться в собственном же коде будет проще, если установка макета в выполняется в представлении (не нужно искать в коде контроллера это представление).

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

      • mc-bear

        Логично. Но по аналогии тогда и регистрация js-скриптов и css тоже должна делаться в лайаутах/представлениях

        $cs = Yii::app()->clientScript;
        //подключаем редактор tinyMce для всех textarea
        $cs->registerScriptFile(Yii::app()->request->baseUrl.'/js/tiny_mce/tiny_mce_gzip.js', CClientScript::POS_END);
        $cs->registerScriptFile(Yii::app()->request->baseUrl.'/js/tiny_mce/init_gz.js' ,CClientScript::POS_END);
        $cs->registerScriptFile(Yii::app()->request->baseUrl.'/js/tiny_mce/init_editor.js' ,CClientScript::POS_END);

        • Да, вы правы, так, наверное, логичнее. Все-таки js код относится к представлению, а не контроллеру.

          Подключать js в макете (layout) имеет смысл только если скрипты используются на всех страницах.

  • Как Вы считаете использовать
    $this->layout = 'dashboard';
    лучше в представлении или в контроллере ?
    Я просто всегда писал это в контроллер, теперь увидел Ваше решение и задумался…

    • На мой вгляд, логичнее в представлении, точнее не логичнее, а ближе к MVC архитектуре. Вообще, если в методе контроллера вызывается только одно представление, то разницы нет. Но если контроллер выбирает какое представление загружать (например, на основе полученных параметров), то удобнее вынести установку макета в представление.

      Кроме того, есть ещё один аргумент. Мне кажется, разбираться в собственном же коде будет проще, если установка макета в выполняется в представлении (не нужно искать в коде контроллера это представление).

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

      • mc-bear

        Логично. Но по аналогии тогда и регистрация js-скриптов и css тоже должна делаться в лайаутах/представлениях

        $cs = Yii::app()->clientScript;
        //подключаем редактор tinyMce для всех textarea
        $cs->registerScriptFile(Yii::app()->request->baseUrl.'/js/tiny_mce/tiny_mce_gzip.js', CClientScript::POS_END);
        $cs->registerScriptFile(Yii::app()->request->baseUrl.'/js/tiny_mce/init_gz.js' ,CClientScript::POS_END);
        $cs->registerScriptFile(Yii::app()->request->baseUrl.'/js/tiny_mce/init_editor.js' ,CClientScript::POS_END);

        • Да, вы правы, так, наверное, логичнее. Все-таки js код относится к представлению, а не контроллеру.

          Подключать js в макете (layout) имеет смысл только если скрипты используются на всех страницах.

  • А вы будете писать посты про продвижение игрового сайта? Создать сайт одно, а вот монетизировать его и получать стабильную прибыль — это совсем другое дело.

    • Влад

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

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

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

  • А вы будете писать посты про продвижение игрового сайта? Создать сайт одно, а вот монетизировать его и получать стабильную прибыль — это совсем другое дело.

    • Влад

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

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

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

  • ShadowX

    А как на счет собрать уроки в PDF ?

    • Я над этим думаю 🙂 На данный момент весь текст в word'е, т.е. получить pdf несложно. Единственный момент, который нужно решить — оформление блоков кода.

      • ShadowX

        А если прямо с сайта скопировать в ворд оформление не подтянется?
        И было бы замечательно, если бы там было содержание которое в боковой панели вивера видно 🙂

        • Проблема в том, что на сайте у блоков кода есть горизонтальная прокрутка, а в pdf её, понятно, не будет, поэтому нужно как-то выделить новые строки и перенесённые. Но, я думаю, как-нибудь эту проблему решу 🙂 Хотелось бы найти какое-нибудь автоматизированное решение. Ковыряться вручную очень не хочется 😉
          А с оглавлением проблем быть не должно.

      • ShadowX

        Может это поможет: http://docutils.sourceforge.net/sandbox/code-block-directive/docs/syntax-highlight.html хотя, разбираться с DocBook только ради одного документа не очень приятно.

        • Спасибо за ссылку! Я почитаю. Может за это время еще что-нибудь полезное о yii напишу 😉

  • ShadowX

    А как на счет собрать уроки в PDF ?

    • Я над этим думаю 🙂 На данный момент весь текст в word'е, т.е. получить pdf несложно. Единственный момент, который нужно решить — оформление блоков кода.

      • ShadowX

        А если прямо с сайта скопировать в ворд оформление не подтянется?
        И было бы замечательно, если бы там было содержание которое в боковой панели вивера видно 🙂

        • Проблема в том, что на сайте у блоков кода есть горизонтальная прокрутка, а в pdf её, понятно, не будет, поэтому нужно как-то выделить новые строки и перенесённые. Но, я думаю, как-нибудь эту проблему решу 🙂 Хотелось бы найти какое-нибудь автоматизированное решение. Ковыряться вручную очень не хочется 😉
          А с оглавлением проблем быть не должно.

      • ShadowX

        Может это поможет: http://docutils.sourceforge.net/sandbox/code-block-directive/docs/syntax-highlight.html хотя, разбираться с DocBook только ради одного документа не очень приятно.

        • Спасибо за ссылку! Я почитаю. Может за это время еще что-нибудь полезное о yii напишу 😉

  • Спасибо! Информация изложена понятно, вопросов нет, все получается!

  • Спасибо! Информация изложена понятно, вопросов нет, все получается!

  • Отличный урок получился. Слава Богу все получилось повторить. Ждем новых уроков.

  • Отличный урок получился. Слава Богу все получилось повторить. Ждем новых уроков.

  • mc-bear

    Владимир, к сожалению твой ответ на мой комментарий мне не помог, а еще более запутал в выборе, как же правильнее поступить чтоб быть ближе к MVC. Если есть аккаунт на форуме русского сообщества yii, присоединяся к дискусии
    http://yiiframework.ru/forum/viewtopic.php?f=4&t=586

  • mc-bear

    Владимир, к сожалению твой ответ на мой комментарий мне не помог, а еще более запутал в выборе, как же правильнее поступить чтоб быть ближе к MVC. Если есть аккаунт на форуме русского сообщества yii, присоединяся к дискусии
    http://yiiframework.ru/forum/viewtopic.php?f=4&t=586

  • Vic

    и переместили в него методы actionLogin и actionLogin

    Прошу прощения: не могу найти различия в именах.

    • Спасибо, что заметили.
      Читать нужно так:
      методы actionLogin и actionLogout

  • Vic

    и переместили в него методы actionLogin и actionLogin

    Прошу прощения: не могу найти различия в именах.

    • Спасибо, что заметили.
      Читать нужно так:
      методы actionLogin и actionLogout

  • Imax Pv

    Добрый день.
    Бьюсь над пунктом «Создать игру» уже часа 2, но все безуспешно.

    В accessRules() добавил create, больше ничего не менял, при заходе на http://localhost/local/games/create/ получаю (выделена 71 строка):

    Описание

    Undefined variable: types
    Исходный код

    /srv/http/local/protected/views/games/_form.php(71)

    00059: <?php echo CHtml::activeDropDownList($model, 'g_state'
    00060: , array('0'=>'Опубликовано','1'=>'Черновик')); ?>
    00061: </div>
    00062: <div class=»types_list»>
    00063: <?php echo 'Жанры:
    '; ?>
    00064: <?php
    00065: $curTypes = $model->ygs_types;
    00066: $curT = array();
    00067: foreach ($curTypes as $type) {
    00068: $curT[] = $type->t_id;
    00069: }
    00070: $allT = array();
    00071: foreach ($types as $type) {
    00072: $allT[$type->t_id] = $type->t_name;
    00073: }
    00074: echo CHtml::checkBoxList('types',$curT,$allT, array('separator'=>''));
    00075: ?>
    00076: </div>
    00077: <div class=»action»>
    00078: <?php echo CHtml::submitButton($update ? 'Сохранить' : 'Создать'); ?>
    00079: </div>
    00080:
    00081: <div class=»action»>
    00082: <?php echo CHtml::link('Просмотр', array('games/show', 'id'=>$model->g_id), array('target'=>'_blank')); ?>
    00083: </div>

    Подскажите пожалуйста, что я делаю не так.
    Спасибо.

  • Вы все делаете так, только в форму (_form.php) я добавил код для вывода выпадающего списка с перечнем жанров. Этот перечень нужно получить в контроллере с помощью запроса к БД.

    В общем, добавьте в контроллер (actionCreate) строку

    $types = Types::model()->findAll();
    и измените
    $this->render('create',array('model'=>$model, 'types'=>$types));

    Но хочу предупредить. При импорт игр id берутся из xml фида партнерки, поэтому если вы начнете сами создавать игры, возможны совпадения id'шников.

  • TRF

    Здравствуйте!
    Не получается зайти в админку, делаю все по инструкции. Сайт отображается нормально.

    При http://localhost/dashboard/login получаю:

    выделена 259 строка

    Описание

    require(/srv/http/protected/components/dashboardMenu.php): failed to open stream: No such file or directory
    Исходный код

    /srv/http/framework/YiiBase.php(259)

    00247: return $alias;
    00248: }
    00249:
    00250: if(($className=(string)substr($alias,$pos+1))!=='*' && (class_exists($className,false) || interface_exists($className,false)))
    00251: return self::$_imports[$alias]=$className;
    00252:
    00253: if(($path=self::getPathOfAlias($alias))!==false)
    00254: {
    00255: if($className!=='*')
    00256: {
    00257: self::$_imports[$alias]=$className;
    00258: if($forceInclude)
    00259: require($path.'.php');
    00260: else
    00261: self::$_classes[$className]=$path.'.php';
    00262: return $className;
    00263: }
    00264: else // a directory
    00265: {
    00266: if(self::$_includePaths===null)
    00267: {
    00268: self::$_includePaths=array_unique(explode(PATH_SEPARATOR,get_include_path()));
    00269: if(($pos=array_search('.',self::$_includePaths,true))!==false)
    00270: unset(self::$_includePaths[$pos]);
    00271: }

    • alex

      столкнулся с похожей проблемой, только на локалхосте все работает, а вот на сервере при входе в админку выдает ошибку http://99games.ru/dashboard/login проверял уже несколько раз и названия файлов и их расположение все на месте, уже весь мозг сломал не могу понять что за хрень =
      может там где то кэшируются какие-то файлы и при переносе на сервер с локалхоста их нужно удалять?

      • Нет, файлы там не кешируются.
        Посмотрите в каком регистре записано имя файла dashboardMenu (для *nix систем это важно).

        • Roe-ru

          Владимир, а как в представлении вызвать метод из стороннего контроллера? Спасибо.

        • Я дам ссылку на пример.

          А вообще, если вы столкнулись с необходимостью вызывать методы других контроллеров, может быть подумать об использовании модулей?

        • Roe-ru

          Интересное решение. не мог примеров найти..
          Благодарю за подсказку (модули) и пример!

  • Проверьте, существует ли файл
    /srv/http/protected/components/dashboardMenu.php
    Обратите внимание на регистр букв.