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

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

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

  • Nulpatrol

    Прочитал и понял почему в свое время не выбрал этот фреймворк. Слишком сложно…

    • А что именно сложно? Работа с БД?

      • Nulpatrol

        Просто сложно))) Я привык к Codeigniter, который считаю самым простым в изучении.

        • А мне кажется все достаточно понятным )
          Знаете, при каких обстоятельствах изучение библиотеки становится сложным?
          ИМХО, когда нет никакой документации и приходится лезть в код (особенно если он еще не документирован), и методом «тыка» выискивать нужный функционал, один за другим перебирая классы, которых целые мегабайты. А если это dll-ка, которую можно познать только путем дизассемблирования… то можно сразу вешаться =)
          Если попробовать пару раз разобраться в чужом коде не имея документации, то любые пояснения, даже на зарубежных языках (а тем более на русском), становятся глотком свежего воздуха в познании и понимании функционала.

  • Nulpatrol

    Прочитал и понял почему в свое время не выбрал этот фреймворк. Слишком сложно…

    • А что именно сложно? Работа с БД?

      • Nulpatrol

        Просто сложно))) Я привык к Codeigniter, который считаю самым простым в изучении.

        • А мне кажется все достаточно понятным )
          Знаете, при каких обстоятельствах изучение библиотеки становится сложным?
          ИМХО, когда нет никакой документации и приходится лезть в код (особенно если он еще не документирован), и методом «тыка» выискивать нужный функционал, один за другим перебирая классы, которых целые мегабайты. А если это dll-ка, которую можно познать только путем дизассемблирования… то можно сразу вешаться =)
          Если попробовать пару раз разобраться в чужом коде не имея документации, то любые пояснения, даже на зарубежных языках (а тем более на русском), становятся глотком свежего воздуха в познании и понимании функционала.

  • Для меня это тоже сложновато было понять, но все равно спасибо!

  • Для меня это тоже сложновато было понять, но все равно спасибо!

  • Как по мне, так лучше Codeignite еще ничего непридумано!

  • Как по мне, так лучше Codeignite еще ничего непридумано!

  • xax

    Хороший у вас сайт!

  • xax

    Хороший у вас сайт!

  • alex

    спасибо за уроки, поставил фреймворк, установил скрипт, выдает ошибку:
    Рабочий путь приложения «/home/j/jeronimo/retouch/public_html/protected/runtime» задан неверно.
    ————————
    Куда копать дальше?
    ссылка на тестовый сайт http://fashionretouch.ru/

    • alex

      ответ нашел через гугл на этом же сайте в комментах ко второй части уроков ))

  • alex

    оставил в базе одну запись, остальные удалил, в результате ничего не происходит и ошибок не выдает, как можно понять в чем проблема, то ли данные не парсятся из фида то ли не записываются в базу, или парсинг нужно как-то специально запускать? Если я правильно понял то все автоматом должно парсится и инсертится в базу?

    • Да, все должно работать автоматом.
      Только что проверил. Импортировалось — 148 новых игр (вышли за последнее время).

      Прежде всего, проверьте фид (листинг 2 сверху). Его можно просто вставить в адресную строку браузера.

      Убедитесь, что время работы скрипта не превышает допустимое для хостинга (вставка несколько сот записей в базу занимает некоторое время).

      Включите лог
      'log'=>array(
      'class'=>'CLogRouter',
      'routes'=>array(
      array(
      //выводим лог внизу страницы
      'class'=>'CWebLogRoute',
      'levels'=>'trace, info, profile',
      ),
      ),
      ),

      вы увидите все выполненные запросы

      • alex

        увеличил время выполнения скрипта в php.ini и все заработало, спасибо

  • Аноним

    set_time_limit(0);

  • Alisher Davronov

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

    а relations не решения?

    • Да, можно сделать с использованием relations. Разницы не будет. Запросы теже самые. Если хотите глубже изучить библиотеки yii, то однозначно нужно использовать relations.

  • Юрий Емельянов

    Самую большую загвоздку как начинающему, создало декодирование типов игр.
    Не понятно зачем происходит постоянное кодирование/декодирования типов игры.
    Зачем в таблице ygs_games поле g_type хранятся кодированные значения типов игр когда в таблице ygs_games_types эти данные присутствуют в нормализованном виде.
    Потратил пару дней пока понял что к чему и как это работает.

    • Декодирование типов используется для работы с отношениями один-ко-многим. Т.е. например выбор игр, которые относятся к данной категории.
      В кодированном данном виде приходят от партнерки.

  • Guest

    Заметил одну вещь, если на сервере Apache, не установлено расширение PDO, то привьюшки не сохраняются 😀

  • Заметил одну вещь, если на сервере Apache, не установлено расширение PDO в php, то привьюшки не сохраняются 😀

    • Если у вас не установлено PDO, то проблема будет не только с превьюшками 🙂 Библиотека Yii для работы с базой данных построена на основе PDO.