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

16 января, 2010
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.

  1. public function actionImport() {
  2.     //получаем список всех жанров
  3.     $types = Types::model()->findAll();
  4.     //ищем все сохраненные игры (их id)
  5.     $existingIds = Games::model()->getExistingIds();
  6.     $errors = array();
  7.     $results = ";
  8.     //обработка команды
  9.     if (isset($_POST['import'])) {
  10.         libxml_use_internal_errors(true);
  11.         //загружаем xml фид
  12.         $xml = simplexml_load_file(Yii::app()->user->xml);
  13.         if (!$xml) {
  14.             $errors = libxml_get_errors();
  15.         }
  16.         else {
  17.             $i = 0;
  18.             //парсинг фида
  19.             foreach ($xml->result->ITEM as $game) {
  20.                 //если эта игра уже сохранена…
  21.                 if (in_array($game->ID, $existingIds)) {
  22.                     //…переходим к следующей
  23.                     continue;
  24.                 }
  25.                 //создаем новую игру
  26.                 $newGame = new Games;
  27.                 //заполняем атрибуты
  28.                 $newGame->g_id = $game->ID;
  29.                 $newGame->g_rate = $game->RATE;
  30.                 $newGame->g_name_url = $game->NAME_URL;
  31.                 $newGame->g_type = $game->TYPE;
  32.                 $newGame->g_added = $game->ADDED;
  33.                 $newGame->g_size = $game->SIZE;
  34.                 $newGame->g_name = $game->NAME;
  35.                 $newGame->g_medium_pic = $game->MEDIUM_PIC;
  36.                 $newGame->g_small_pic = $game->SMALL_PIC;
  37.                 $newGame->g_download_link = $game->DOWNLOAD_LINK;
  38.                 $newGame->g_shortdescr = $game->SHORTDESCR;
  39.                 $newGame->g_fulldescr = $game->FULLDESCR;
  40.                 $newGame->g_publish_date = date('Y-m-d', time());
  41.                 $newGame->g_state = Games::PUBLISHED;
  42.                 //записываем массив со скриншотами
  43.                 foreach ($game->SCREENSHOT as $sh) {
  44.                     $newGame->g_screenshots[] = $sh;
  45.                 }
  46.                 //разбираем поле с жанрами
  47.                 foreach ($types as $type) {
  48.                     if ($newGame->g_type & $type['t_id']) {
  49.                         $newGame->g_types[] = $type['t_id'];
  50.                     }
  51.                 }
  52.                 //сохраняем игру в БД
  53.                 //сохранение скриншотов и жанров выполняетя в Games::afterSave()
  54.                 if (!$newGame->save()) {
  55.                     $errors[] = 'Не могу сохранить игру id = '.$newGame->g_id;
  56.                 }
  57.                 else {
  58.                     $i++;
  59.                 }
  60.             }
  61.             $results = 'Сохранено новых игр '.$i;
  62.         }
  63.     }
  64.     //показываем форму
  65.     $this->render('import',
  66.         array('xml'=>Yii::app()->user->xml, 'errors'=>$errors, 'results'=>$results));
  67. }

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

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

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

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

  1. 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, но нам нужно сохранить данные о типах игры и скриншотах.

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

  1. class Games extends CActiveRecord {
  2.     public $g_screenshots = array();
  3.     public $g_types = array();
  4.     //это свойство используется для того, чтобы отключить удаление
  5.     //скриншотов при обновлении страницы, т.к. форма обновления не
  6.     //содержит списка скриншотов
  7.     public $updateScreenshots = true;
  8.  
  9.     const PUBLISHED = 0;
  10.     const DRAFT = 1;
  11. }

$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().

  1. public function afterSave() {
  2.     //если запись уже существует, то перед обновлением удаляем все связанные данные
  3.     if (!$this->isNewRecord) {
  4.         $this->dbConnection->createCommand('DELETE FROM ygs_games_types WHERE gt_game_id='.$this->g_id)->execute();
  5.         //скриншоты удаляем только если updateScreenshots == true
  6.         if ($this->updateScreenshots) {
  7.             $this->dbConnection->createCommand('DELETE FROM ygs_screenshots WHERE s_game_id='.$this->g_id)->execute();
  8.         }
  9.     }
  10.     //сохраняем жанры
  11.     foreach ($this->g_types as $type) {
  12.         if (($t = Types::model()->findByPk($type)) !== null) {
  13.             $this->dbConnection->createCommand('INSERT INTO ygs_games_types (gt_game_id, gt_type_id) VALUES ('.$this->g_id.','.$type.')')->execute();
  14.         }
  15.     }
  16.     //сохраняем скриншоты
  17.     if ($this->updateScreenshots) {
  18.         $command = $this->dbConnection->createCommand('INSERT INTO ygs_screenshots (s_game_id, s_image, s_thumbnail) VALUES (:s_game_id, :s_image, :s_thumbnail)');
  19.         foreach ($this->g_screenshots as $screenshot) {
  20.             $command->bindParam(':s_game_id', $this->g_id, PDO::PARAM_INT);
  21.             $command->bindParam(':s_image', $screenshot->IMAGE, PDO::PARAM_STR);
  22.             $command->bindParam(':s_thumbnail', $screenshot->THUMBNAIL, PDO::PARAM_STR);
  23.             $command->execute();
  24.         }
  25.     }
  26. }

Тут нужны некоторые пояснения. Прежде всего, взгляните на использование свойства 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, содержит форму с одной кнопкой «Импортировать». После импорта мы показываем количество сохранённых игр.

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

архив с исходным кодом

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

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

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

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

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

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

Понравилась статья? Подписывайтесь на продолжение rss link !

Или на мой твиттер twitter link

]]>

Добавьте эту страницу в google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru

]]>

Опубликовано в PHP, Web разработка, Yii View Comments

]]>
  • xax
    Хороший у вас сайт!
  • Как по мне, так лучше Codeignite еще ничего непридумано!
  • Для меня это тоже сложновато было понять, но все равно спасибо!
  • Nulpatrol
    Прочитал и понял почему в свое время не выбрал этот фреймворк. Слишком сложно...
  • А что именно сложно? Работа с БД?
  • Nulpatrol
    Просто сложно))) Я привык к Codeigniter, который считаю самым простым в изучении.
  • А мне кажется все достаточно понятным )
    Знаете, при каких обстоятельствах изучение библиотеки становится сложным?
    ИМХО, когда нет никакой документации и приходится лезть в код (особенно если он еще не документирован), и методом "тыка" выискивать нужный функционал, один за другим перебирая классы, которых целые мегабайты. А если это dll-ка, которую можно познать только путем дизассемблирования... то можно сразу вешаться =)
    Если попробовать пару раз разобраться в чужом коде не имея документации, то любые пояснения, даже на зарубежных языках (а тем более на русском), становятся глотком свежего воздуха в познании и понимании функционала.
blog comments powered by Disqus ]]>