Pull to refresh

Drupal: пишем свой парсер для Feeds

Reading time 5 min
Views 13K
Модуль Feeds является очень популярным среди Drupal-разработчиков. Но возникает вопрос, что делать если необходимо несколько расширить его функциональность. В этом нам поможет система плагинов модуля Feeds.
Существует 3 вида плагинов от которых необходимо наследовать новые:
  • FeedsFetcher — плагин сборщика. Cтандартные: HTTP и File Upload. С помощью этого типа плагинов можно добавить новый источник данных.
  • FeedsProcessor — плагин обработки сущностей. Cтандартные: Node processor, Taxonomy term processor, User processor. С помощью нового плагина можно добавить новый обработчик, который будет создавать особенные сущности, не вписывающиеся в стандартный набор.
  • FeedsParser — плагин парсера. Стандартные парсеры в Feeds это XML, CSV и многие другие.

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

Создание модуля

Как и обычно для создания модуля нам нужно создать info файл и файл модуля:

json_example_parser.info
name = Json example parser
description = Simple json parser
package = Feeds
core = 7.x
dependencies[] = feeds


json_example_parser.module
<?php
/**
 * @file
 * Json example parser - simple parser plugin
 */

/**
 * Implementation of hook_feeds_plugins().
 * Регистрация плагина в системе.
 */
function json_example_parser_feeds_plugins() {
  $info = array();
  $info['JsonExampleParser'] = array(
    'name' => 'JSON parser',
    'description' => 'Parses custom JSON.',
    'handler' => array(
      'parent' => 'FeedsParser', // родительский класс от которого наследуем парсер, стандартные классы Feeds - FeedsFetcher, FeedsParser и FeedsProcessor
      'class' => 'JsonExampleParser', // название парсера, должно совпадать с ключом в массиве
      'file' => 'JsonExampleParser.inc', // файл класса парсера
      'path' => drupal_get_path('module', 'json_example_parser'), // путь к классу парсера
    ),
  );
  return $info;
}

// очистка кеша плагинов Feeds при включении модуля
function json_example_parser_enable() {
  //clear the cache to display in Feeds as available plugin.
  cache_clear_all('plugins:feeds:plugins', 'cache');
}
?>

В комментариях описаны основных моменты хуков.

Исходные данные

Для примера за импортируемый материал возьмем ноду “Компьютерная игра” со следующими полями:

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

Входной JSON:

[
  { "name":"Team Fortress 2",
    "year":2007,
    "price":0
  },
  { "name":"Warcraft III: The Frozen Throne",
    "year":2003,
    "price":13.9
  },
  { "name":"Diablo III",
    "year":2012,
    "price":33
  }
]


Создание класса парсера

Для того чтобы создать класс парсера его нужно наследовать от стандартного абстрактного класса FeedsParser и переопределить методы parse и getMappingSources. Переопределение остальных методов не является обязательным.

JsonExampleParser.inc
<?php
/**
 * A JSON example parser
 */
class JsonExampleParser extends FeedsParser {
  /**
   * Implements FeedsParser::parse().
   */
  public function parse(FeedsSource $source, FeedsFetcherResult $fetcher_result) {
    $result = new FeedsParserResult();
    // получаем последние настройки
    $source_config = $source->getConfigFor($this);
    // извлекаем JSON данные
    $fetch_items = json_decode($fetcher_result->getRaw());
    foreach ($fetch_items as $value) {
      $item = array('name' => $value->name);
      if ($value->year) {
        $item['year'] = intval($value->year);
      }
      if ($value->price) {
        $item['price'] = floatval($value->price);
      }
      // применение настроек из формы
      if (  $source_config['type'] == 'all' ||
            ($source_config['type'] == 'free' && $item['price'] == 0) ||
            ($source_config['type'] == 'paid' && $item['price'] > 0)) {
        // добавляем запись для импорта
        $result->items[] = $item;
      }
    }
    return $result;
  }

  /**
   * Implements FeedsParser::getMappingSources().
   */
  public function getMappingSources() {
    return array(
      'name' => array(
        'name' => t('Game name'),
        'description' => t('The name of the computer game.'),
      ),
      'year' => array(
        'name' => t('Release year'),
        'description' => t('Release year of the computer game.'),
      ),
      'price' => array(
        'name' => t('Game price'),
        'description' => t('The cost of the computer game.'),
      ),
    ) + parent::getMappingSources();
  }

  /**
   * Configuration form.
   * Конфигурационная форма, которая будет отображаться на странице настройки импортера.
   */
  public function configForm(&$form_state) {
    $form = array();
    $form['type'] = array(
      '#type' => 'select',
      '#title' => t('Game type'),
      '#description' => t('Game filter by type.'),
      '#options' => array(
        'all' => t('All game'),
        'paid' => t('Paid'),
        'free' => t('Free'),
      ),
      '#default_value' => $this->config['type'],
    );
    return $form;
  }

  /**
   * Define default configuration values.
   * Стандартные настройки для парсера, которые будут применены если форма открыта впервые.
   */
  public function configDefaults() {
    return array(
      'type' => 'all',
    );
  }

  /**
   * Define defaults.
   * Определение настроек отправленных из формы.
   */
  public function sourceDefaults() {
    return array(
      'type' => $this->config['type'],
    );
  }

  /**
   * Show configuration form for users.
   * Конфигурационная форма которая будет отображаться пользователям на странице импорта.
   */
  public function sourceForm($source_config) {
    $form = array();
    $form['#weight'] = -10;
    $form['help']['#markup'] = '<div class="help"><p>' . t('Select the type of game you want to import') . ':</p></div>';
    $form['type'] = array(
      '#type' => 'select',
      '#title' => t('Game type'),
      '#description' => t('Game filter by type.'),
      '#options' => array(
        'all' => t('All game'),
        'paid' => t('Paid'),
        'free' => t('Free'),
      ),
      '#default_value' => isset($source_config['type']) ? $source_config['type'] : 'all',
    );
    return $form;
  }
}


Немного о методах.

parse — метод парсинга, получает объект класса источника FeedsSource и объект класса FeedsFetcherResult из которого извлекаются считанные данные. Данный метод формирует готовый объект FeedsParserResult с набором сущностей items для сохранения.

getMappingSources — метод который определяет поля которые будут доступны из источника для записи в поля создаваемого объекта. Например в данном случае поле маппинга name будет записываться в заголовок ноды и т.д.

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

configDefaults — стандартные настройки, если пользователь не использовал форму для конфигурации парсера.

sourceDefaults — переопределенный метод для получения доступа к сохраненным параметрам из формы конфигурации.

sourceForm — форма которая будет доступна пользователям при импорте, дополняя форму Fetcher’a.

Заключение

В результате мы получили полноценный плагин для модуля Feeds с неограниченными возможностями кастомизации настроек и обработки любых входных данных. Для более детального представление о плагинах Feeds советую посмотреть исходные коды стандартных дополнений, которые идут вместе с модулем.
Tags:
Hubs:
+5
Comments 3
Comments Comments 3

Articles