Yii PHP framework: создаём игровой сайт. Часть 6. Формируем страницы игр и жанров.

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

yii_game_site_mvc

Приветствую всех! Сегодня мы продолжим разработку игрового сайта на основе фреймворка Yii.

Напомню, на чём мы остановились в прошлый раз. У нас создан контроллер для работы с играми GamesController, модель Games и стандартные представления (находятся в папке views/games). Кроме того, мы написали метод импорта игр actionImport.

Раз импортировать игры в базу мы можем, напишем методы для их отображения на сайте. Всего нам нужно создать три типа страниц:

1) с общим перечнем игр (главная);

2) с перечнем игр определенного жанра;

3) с выбранной игрой.

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

Теперь взгляните на сам метод.

public function actionList()
{
	$type = null;
	//формируем запрос на поиск игр с сортировкой по дате
	$criteria=new CDbCriteria;
	$criteria->order = 'g_added DESC';
	//если указан параметр type_id, нужно показывать только игры выбранного жанра
	if (isset($_GET['type_id']) && is_numeric($_GET['type_id'])) {
		//ищем указанный жанр
		$type = Types::model()->findByPk($_GET['type_id']);
		//если указанный жанр найден...
		if (null !== $type) {
			//...добавляем в запрос дополнительный параметр
			$criteria->condition = 't_id=:t_id';
			$criteria->params = array(':t_id'=>$_GET['type_id']);
		}
	}

	//получаем данные для пагинации
	if (null !== $type) {
		$pages=new CPagination(Games::model()->published()->with('ygs_types')->count($criteria));
	} else {
		$pages=new CPagination(Games::model()->published()->count($criteria));
	}
	$pages->pageSize=self::PAGE_SIZE;
	$pages->applyLimit($criteria);

	//получаем список игр
	if (null !== $type) {
		//ВАЖНО! Вызов published должен идти до with
		$models=Games::model()->published()->with('ygs_types')->findAll($criteria);
	} else {
		$models=Games::model()->published()->findAll($criteria);
	}
	
	//если ни одной страницы не найдено, отправляем 404-ую ошибку
	if (empty($models)) {
		throw new CHttpException(404,'The requested page does not exist.');
	}

	//заполняем массив с жанрами игр
	foreach ($models as $key=>$game) {
		//расшифровываем жанры игр (по коду в поле g_type)
		$models[$key]->g_types = $this->_decodeTypes($game->g_type);
	}
	//показываем страницу
	$this->render('list',array(
		'models'=>$models,
		'pages'=>$pages,
		'type'=>$type
	));
}

Обратите внимание на то, как метод определяет какой тип страницы мы хотим сформировать. Если передан GET параметр type_id, то нужно сформировать страницу с играми определённого жанра. Иными словами, запрос вида
http://yiigame.l/index.php?r=games/list
сформирует страницу с общим перечнем игр, а запрос
http://yiigame.l/index.php?r=games/list&type_id=8
страницу с играми жанр которых имеет id == 8.

Как обычно для формирования условий поиска мы используем объект CDbCriteria. И в первую очередь устанавливаем его свойство
$criteria->order = 'g_added DESC';
в результате новые игры окажутся в начале списка.

Если получен параметр $_GET['type_id'] мы ищем указанный жанр (строка 10) и добавляем условие в запрос (строки 14 и 15). При этом используется тот же синтаксис объявления параметров, что и в PDO. Параметру присваивается имя, которое начинается с двоеточия, а затем, используя это имя, мы устанавливаем значение.

Затем выполняем настройку пагинации (с помощью класса CPagination). Тут появляется один интересный момент. Нам нужно просто посчитать общее количество игр (строка 21), но игры имеют два состояния: опубликована и черновик. Нас интересуют только опубликованные, т.е. те у которых в БД поле g_state == 0 (таблица ygs_games).

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

Мы можем в модели (Games) объявить метод scopes, который выглядит следующим образом.

public function scopes() {
	return array(
		'published'=>array(
			'condition'=>'g_state='.self::PUBLISHED,
		)
	);
}

Он возвращает массив в котором ключом является название метода, а значением – параметры запроса к БД. В данном случае, в нашем распоряжении появится метод published, который можно использовать при формировании запроса.

Например, при вызове
Games::model()->published()->count(…);
в запрос будет вставлено условие 'g_state='.self::PUBLISHED.

Называется эта возможность «Именованные группы условий».

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

Возвращаемся к методу actionList.

Нам нужно получить список игр (строки 29-34). Для страницы с общим перечнем игр, запрос формируется так:

$models=Games::model()->published()->findAll($criteria);

Тут всё просто, но когда нам нужно выбрать только игры конкретного жанра, мы должны использовать в запросе таблице ygs_types и ygs_games_types. Естественно, библиотека фреймворка позволяет сократить работу и не писать такой запрос вручную. Достаточно сделать такой вызов

$models=Games::model()->published()->with('ygs_types')->findAll($criteria);

Обратите внимание на метод with. В его параметре указан элемент массива, который возвращает метод relations модели.

public function relations()
{
	return array(
		//указываем, что в результам запроса нужно включить данные из связанной таблицы
		'ygs_types' => array(self::MANY_MANY, 'Types', 'ygs_games_types(gt_game_id, gt_type_id)','together'=>true,'joinType'=>'INNER JOIN'),
		'ygs_screenshots' => array(self::HAS_MANY, 'Screenshots', 's_game_id'),
	);
}

Консольная утилита Yii автоматически создаёт только два параметра self::MANY_MANY, 'Types', 'ygs_games_types(gt_game_id, gt_type_id)', которые указывают тип связи между таблицами. Но мы добавим ещё два 'together'=>true и 'joinType'=>'INNER JOIN'. С помощью первого мы указываем, что связанная таблица (в данном случае это таблица жанров) должна войти в результаты запроса, второй – изменяет тип объединения (по-умолчанию используется LEFT OUTER JOIN).

После этого, мы проверяем были ли найдены игры (строки 37-39) и если нет – возвращаем 404-ую ошибку.

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

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

public function _decodeTypes($value) {
	if (count($this->_allTypes) == 0) {
		//получаем список жанров
		$this->_allTypes = Types::model()->findAll();
	}
	$types = array();
	//перебираем все жанры и проверяем указаны ли они в поле жанра игры
	//для этого используется логическая операция "И" 
	foreach ($this->_allTypes as $type) {
		if ((int)$value & (int)$type->t_id) {
			$types[] = $type;
		}
	}
	//возвращаем массив с жанрами
	return $types;
}

В качестве параметра он получает список жанров в закодированной форме, а возвращает массив с объектами типа CActiveRecord для каждого жанра к которому относится игра. (В начале метода мы получаем полный список жанров, а затем ищем те, которые относятся к данной игре и добавляем их в массив $types).

Заключительный этап – загрузка представления (list). Полностью приводить его код я не буду (вы можете посмотреть его в архиве), покажу только самую интересную часть.

$i = 0;
foreach($models as $n=>$model) {
	$this->renderPartial('_short_desc', array('game'=>$model));
	if ($i % 2 !== 0) {
		echo '<div class="clear"></div>';
	}
	$i++;
}

Это цикл, в котором формируем короткое описание мы, каждой найденной игры. Как видите, разметка для этого описания находится в отдельном представлении _short_desc, которое мы загружаем с помощью метода renderPartial. Отдельное преставление удобно использовать так как в дальнейшем мы создадим виджет «лучшие игры», который будет показывать точно такие же описания игр.

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

Напоминаю, что вы можете скачать архив с этим примером

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