Сегодня мы продолжим создание игрового сайта с помощью 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
Данные выглядят следующим образом.

В каждую отдельную игру (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Как видите, импортировать игры не сложно. Главное — правильно записать данные в базу.
Если у вас есть вопросы или замечания, пишите. В следующий раз мы займемся созданием страниц с перечнем игр.
Все разделы цикла.
- Yii PHP framework: создаём игровой сайт. Часть 1. Постановка задачи.
- Yii PHP framework: создаём игровой сайт. Часть 2. База данных и установка фреймворка.
- Yii PHP framework: создаём игровой сайт. Часть 3. Аутентификация.
- Yii PHP framework: создаём игровой сайт. Часть 4. Работа с жанрами игр.
- Yii PHP framework: создаём игровой сайт. Часть 5. Импорт игр.
- Yii PHP framework: создаём игровой сайт. Часть 6. Формируем страницы игр и жанров.
- Yii PHP framework: создаём игровой сайт. Часть 7. Работа с JavaScript и страницы игр.
- Yii PHP framework: создаём игровой сайт. Часть 8. Создаём виджеты.
- Yii PHP framework: создаём игровой сайт. Часть 9. Поиск ошибок.
- Yii PHP framework: создаём игровой сайт. Часть 10. Панель управления.
- Yii PHP framework: создаём игровой сайт. Часть 11. Человекопонятные URL.
- Архив с исходниками
Интересно почитать
Хороший блог о веб-дизайне расскажет как пользоваться фотошопом.


