Yii PHP framework: создаём игровой сайт. Часть 5. Импорт игр.

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

import games

Сегодня мы продолжим создание игрового сайта с помощью Yii PHP framework. Наши задачи: заполнить базу данных информацией об играх (её мы берем из партнерки GameBoss) и, заодно, немного разобраться с библиотекой Yii для работы с базой данных.

Алгоритм импорта довольно прост.

1) Читаем из базы список жанров. Они должны быть созданы заранее, иначе мы не сможем правильно создать записи в таблице ygs_games_types.

2) Получаем список игр, которые уже сохранены в БД (нам нужны их id).

3) Получаем данные от GameBoss. Они приходят в xml формате.

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

5) Создаём записи в связанных таблицах ygs_games_types и ygs_screenshots.

6) Показываем страницу с кнопкой «Импрорт».

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

>> yiic shell
model Games ygs_games
crud Games

Теперь можно добавить метод actionImport в контроллер GamesController.

public function actionImport() {
	//получаем список всех жанров
	$types = Types::model()->findAll();
	//ищем все сохраненные игры (их id)
	$existingIds = Games::model()->getExistingIds();
	$errors = array();
	$results = '';
	//обработка команды
	if (isset($_POST['import'])) {
		libxml_use_internal_errors(true);
		//загружаем xml фид
		$xml = simplexml_load_file(Yii::app()->user->xml);
		if (!$xml) {
			$errors = libxml_get_errors();
		}
		else {
			$i = 0;
			//парсинг фида
			foreach ($xml->result->ITEM as $game) {
				//если эта игра уже сохранена...
				if (in_array($game->ID, $existingIds)) {
					//...переходим к следующей
					continue;
				}
				//создаем новую игру
				$newGame = new Games;
				//заполняем атрибуты
				$newGame->g_id = $game->ID;
				$newGame->g_rate = $game->RATE;
				$newGame->g_name_url = $game->NAME_URL;
				$newGame->g_type = $game->TYPE;
				$newGame->g_added = $game->ADDED;
				$newGame->g_size = $game->SIZE;
				$newGame->g_name = $game->NAME;
				$newGame->g_medium_pic = $game->MEDIUM_PIC;
				$newGame->g_small_pic = $game->SMALL_PIC;
				$newGame->g_download_link = $game->DOWNLOAD_LINK;
				$newGame->g_shortdescr = $game->SHORTDESCR;
				$newGame->g_fulldescr = $game->FULLDESCR;
				$newGame->g_publish_date = date('Y-m-d', time());
				$newGame->g_state = Games::PUBLISHED;
				//записываем массив со скриншотами
				foreach ($game->SCREENSHOT as $sh) {
					$newGame->g_screenshots[] = $sh;
				}
				//разбираем поле с жанрами
				foreach ($types as $type) {
					if ($newGame->g_type & $type['t_id']) {
						$newGame->g_types[] = $type['t_id'];
					}
				}
				//сохраняем игру в БД
				//сохранение скриншотов и жанров выполняетя в Games::afterSave()
				if (!$newGame->save()) {
					$errors[] = 'Не могу сохранить игру id = '.$newGame->g_id;
				}
				else {
					$i++;
				}
			}
			$results = 'Сохранено новых игр '.$i;
		}
	}
	//показываем форму
	$this->render('import',
		array('xml'=>Yii::app()->user->xml, 'errors'=>$errors, 'results'=>$results));
}

Как видите, он полностью соответствует описанному алгоритму.
С помощью Types::model()->findAll() мы получаем массив со всеми жанрами игр.

Далее получаем массив всех уже записанных в БД игр (с помощью Games::model()->getExistingIds()). Обратите внимание, что id игр мы берем из фида партнерки, т.е., используя этот id, мы всегда можем сравнить игру в базе и игру в фиде. Мы не можем указать какие именно игры нужно получить из базы партнерки, можно указать только их количество. Поэтому мы запрашиваем фид со всеми играми, и если в нём появились новые, сохраняем их в базе.

Для парсинга фида используем библиотеку SimpleXML (на мой взгляд, она одна из самых удобных для работы с XML).

Фид можно получить, используя примерно такой запрос

http://gameboss.ru/x2.php?partner=38370&limit=1000&genre=127&short=1&full=1&image=1

Данные выглядят следующим образом.

xml items

В каждую отдельную игру (item) входят следующие данные.

xml one item

Теперь взгляните на строки 26-41 и сравните их с последним скриншотом. Как видите, мы получаем данные игры, используя имена тегов.

Для каждой новой игры мы создаём новый объект типа Game (строка 26) (класс Games находится в папке protected\models\Games.php – это наша модель).

Модель автоматически предоставляет нам доступ ко всем полям в таблице ygs_games, но нам нужно сохранить данные о типах игры и скриншотах.

Поэтому добавим в модель несколько свойств.

class Games extends CActiveRecord {
...
	public $g_screenshots = array();
	public $g_types = array();
	//это свойство используется для того, чтобы отключить удаление
	//скриншотов при обновлении страницы, т.к. форма обновления не
	//содержит списка скриншотов
	public $updateScreenshots = true;

	const PUBLISHED = 0;
	const DRAFT = 1;
}

$g_screenshots и $g_types используются для хранения скриншотов и жанров, которые относятся к данной игре.

Вернемся к методу actionImport. Мы заполняем массивы $g_screenshots и $g_types соответствующими данными (строки 43-51), и сохраняем игру (строка 54).

Для того, чтобы получить жанры игр, мы используем побитную операцию сложения. Жанры имеют коды: 1, 2, 4, 8, 16 и т.д., т.е. двойка в соответствующей степени. Поэтому если код жанра 3 (0000.0011), то это значит, что игра относится к жанрам 1 (0000.0001) и 2 (0000.0010).

Библиотека Yii сама запишет данные в таблицу ygs_games, т.к. модель Games создана по этой таблице. Но сохранить скриншоты и жанры игр она сама не сможет. Поэтому мы должны сделать это вручную.

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

public function afterSave() {
	//если запись уже существует, то перед обновлением удаляем все связанные данные
	if (!$this->isNewRecord) {
		$this->dbConnection->createCommand('DELETE FROM ygs_games_types WHERE gt_game_id='.$this->g_id)->execute();
		//скриншоты удаляем только если updateScreenshots == true
		if ($this->updateScreenshots) {
			$this->dbConnection->createCommand('DELETE FROM ygs_screenshots WHERE s_game_id='.$this->g_id)->execute();
		}
	}
	//сохраняем жанры
	foreach ($this->g_types as $type) {
		if (($t = Types::model()->findByPk($type)) !== null) {
			$this->dbConnection->createCommand('INSERT INTO ygs_games_types (gt_game_id, gt_type_id) VALUES ('.$this->g_id.','.$type.')')->execute();
		}
	}
	//сохраняем скриншоты
	if ($this->updateScreenshots) {
		$command = $this->dbConnection->createCommand('INSERT INTO ygs_screenshots (s_game_id, s_image, s_thumbnail) VALUES (:s_game_id, :s_image, :s_thumbnail)');
		foreach ($this->g_screenshots as $screenshot) {
			$command->bindParam(':s_game_id', $this->g_id, PDO::PARAM_INT);
			$command->bindParam(':s_image', $screenshot->IMAGE, PDO::PARAM_STR);
			$command->bindParam(':s_thumbnail', $screenshot->THUMBNAIL, PDO::PARAM_STR);
			$command->execute();
		}
	}
}

Тут нужны некоторые пояснения. Прежде всего, взгляните на использование свойства isNewRecord (строка 3). Библиотека Yii сама его изменяет, и оно позволяет определить, работаем ли мы с новой игрой или она была ранее сохранена в БД.

В данном случае, если игра была сохранена ранее, мы удаляем все связанные с ней жанры и скриншоты, т.е. все записи из таблиц ygs_games_types и ygs_screenshots у которых game_id равен id данной игры (строки 3-9).

Затем мы сохраняем скриншоты и жанры из массивов g_screenshots и g_types в базе (строки 11-25).

Вы, наверное, заметили, что здесь мы работаем с базой напрямую, т.е. сами создаём SQL запросы. Фреймворк предоставляет такую возможность, т.к. не во всех случаях удобно использовать его AR библиотеку.

Получить текущее соединение можно с помощью свойства $this->dbConnection, а для выполнения запроса используются методы execute() и query() (первый для отправки запросов на запись/изменение/удаление, а второй – для чтения).

Для формирования запроса используется метод createCommand. Взгляните на строки 18-22. Если вы раньше работали с PDO, то сразу поймете принцип. Библиотека Yii работает на основе PDO, поэтому нет ничего удивительного, что установка параметров запроса выполняется точно также.

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

Представление, которое формирует метод actionImport, содержит форму с одной кнопкой «Импортировать». После импорта мы показываем количество сохранённых игр.

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

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

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

Хороший блог о веб-дизайне расскажет как пользоваться фотошопом.