Как связать сущность с модулем Views в Drupal

В предыдущем уроке я рассказал как создать свою сущность. Сегодня я покажу, как связать сущность с модулем Views. Не забываем скачать его, если еще не сделали этого. Для решения данной задачи существует два способа и сейчас мы их рассмотрим.

Способ 1

Сразу отмечу, что данный способ подходит только если сущность была создана с помощью модуля Entity API. В хуке hook_entity_info() для нашей сущности устанавливаем views controller class:

'views controller class' => 'EntityDefaultViewsController'

Хук стал выглядеть следующим образом:

/**
 * Implements hook_entity_info().
 */
function product_entity_info() {
  $return = array(
    'product' => array(
      'label' => t('Product'),
      'entity class' => 'Entity',
      'controller class' => 'EntityAPIController',
      'views controller class' => 'EntityDefaultViewsController',
      'base table' => 'product',
      'uri callback' => 'product_uri',
      'fieldable' => TRUE,
      'entity keys' => array(
        'id' => 'id',
      ),
      'bundles' => array(
        'product' => array(
          'label' => t('Product'),
          'admin' => array(
            'path' => 'admin/config/product/products',
            'access arguments' => array('configure products settings'),
          ),
        ),
      ),
      'view modes' => array(
        'full' => array(
          'label' => t('Full'),
          'custom settings' => TRUE,
        ),
        'administrator' => array(
          'label' => t('Administrator'),
          'custom settings' => TRUE,
        ),
      ),
      'module' => 'product',
    ),
  );

  return $return;
}

Вот и все, чистим кеш и можем создавать вьюхи.

Способ 2

Для начала имплементируем хук hook_views_api():

/**
 * Implements hook_views_api().
 */
function product_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'product') . '/views',
  );
}

Далее в корне модуля создаем каталог views, а внутри него файл product.views.inc. Открываем этот файл и имплементируем в этом файле хук hook_views_data(). В этом хуке мы опишем доступные поля, фильтры, сортировки и связи:

/**
 * Implements hook_views_data().
 */
function product_views_data() {
  $data = array();

  // Значение ключа 'group' будет использовано в UI для группировки полей,
  // фильтров, сортировки и т.д.
  $data['product']['table']['group']  = t('Product');

  // Описываем базовую таблицу, из которой Views будет доставать данные.
  $data['product']['table']['base'] = array(
    'field' => 'id', // Поле идентификатора.
    'title' => t('Product'),
  );
  
  // Указываем, что таблица хранит данные об сущности.
  $data['product']['table']['entity type'] = 'product';
  
  // Описываем поле "id".
  $data['product']['id'] = array(
    'title' => t('Product ID'), // Человеко-понятное название поля, которое отображается в UI.
    'help' => t('The unique internal identifier of the product.'), // Описание поля, которое отображается в UI.
    // Информация для вывода id.
    'field' => array(
      'handler' => 'product_handler_field_product', // Обработчик, который будет выводить значение поля.
      'click sortable' => TRUE, // Используются для сортировки при отображении данных в таблице.
    ),
    // Информация для принятия id в качестве аргумента.
    'argument' => array(
      'handler' => 'product_handler_argument_product_id', // Обработчик, который будет принимать аргумент, и фильтровать данные по нему.
      'name field' => 'id', // Поле для отображения summary.
      'numeric' => TRUE,
      'validate type' => 'id',
    ),
    // Информация для принятия id в качестве фильтра.
    'filter' => array(
      'handler' => 'views_handler_filter_numeric', // Обработчик, который будет фильтровать данные.
    ),
    // Информация для сортировки по id.
    'sort' => array(
      'handler' => 'views_handler_sort', // Обработчик, который будет сортировать данные.
    ),
  );

  // Описываем поле "заголовок".
  $data['product']['title'] = array(
    'title' => t('Title'),
    'help' => t('The product title.'),
    // Информация для вывода заголовка.
    'field' => array(
      'field' => 'title',
      'group' => t('Product'),
      'handler' => 'product_handler_field_product',
      'click sortable' => TRUE,
      'link_to_product_default' => TRUE,
    ),
    // Информация для принятия заголовка в качестве аргумента.
    'argument' => array(
      'handler' => 'views_handler_argument_string',
    ),
    // Информация для принятия заголовка в качестве фильтра.
    'filter' => array(
      'handler' => 'views_handler_filter_string',
    ),
    // Информация для сортировки по заголовку.
    'sort' => array(
      'handler' => 'views_handler_sort',
    ),
  );
  
  // Описываем поле "Дата создания".
  $data['product']['created'] = array(
    'title' => t('Post date'),
    'help' => t('The date the product was created.'),
    // Информация для вывода даты создания.
    'field' => array(
      'handler' => 'views_handler_field_date',
      'click sortable' => TRUE,
    ),
    // Информация для принятия даты создания в качестве фильтра.
    'filter' => array(
      'handler' => 'views_handler_filter_date',
    ),
    // Информация для сортировки по дате создания.
    'sort' => array(
      'handler' => 'views_handler_sort_date',
    ),
  );

  // Описываем поле "Дата обновления".
  $data['product']['changed'] = array(
    'title' => t('Updated date'),
    'help' => t('The date the product was last updated.'),
    // Информация для вывода даты обновления.
    'field' => array(
      'handler' => 'views_handler_field_date',
      'click sortable' => TRUE,
    ),
    // Информация для принятия даты обновления в качестве фильтра.
    'filter' => array(
      'handler' => 'views_handler_filter_date',
    ),
    // Информация для сортировки по дате обновления.
    'sort' => array(
      'handler' => 'views_handler_sort_date',
    ),
  );

  // Описываем поле "Статус".
  $data['product']['status'] = array(
    'title' => t('Product status'),
    'help' => t('The workflow status of the product.'),
    // Информация для вывода статуса.
    'field' => array(
      'handler' => 'product_handler_field_product_status',
      'click sortable' => TRUE,
    ),
    // Информация для принятия статуса в качестве фильтра.
    'filter' => array(
      'handler' => 'product_handler_filter_product_status',
    ),
    // Информация для сортировки по статусу.
    'sort' => array(
      'handler' => 'views_handler_sort',
    ),
  );

  // Описываем поле "uid".
  $data['product']['uid'] = array(
    'title' => t('User uid'),
    'help' => t('The user author. If you need more fields than the uid add the content: author relationship'),
    // Информация для связи таблицы "product" с таблицей "users".
    'relationship' => array(
      'title' => t('Author'), // Человеко-понятное название связи, которое отображается в UI.
      'help' => t('Relate product to the user who created it.'), // Описание связи, которое отображается в UI.
      'handler' => 'views_handler_relationship',
      'base' => 'users', // Название таблицы, которая будет присоединена, в данном прмиере это таблица "users".
      'field' => 'uid', // По какому полю таблицы будут связаны.
      'label' => t('author'), // Ключевое слово связи, которое используется в UI.
    ),
    // Информация для вывода uid.
    'field' => array(
      'handler' => 'views_handler_field_user',
    ),
    // Информация для принятия uid в качестве аргумента.
    'argument' => array(
      'handler' => 'views_handler_argument_numeric',
    ),
    // Информация для принятия uid в качестве фильтра.
    'filter' => array(
      'handler' => 'views_handler_filter_user_name',
    ),
  );

  // Описываем поле с допустимыми операциями над продуктом.
  $data['product']['operations'] = array(
    // Информация для операций.
    'field' => array(
      'title' => t('Operations links'),
      'help' => t('Display all the available operations links for the product.'),
      'handler' => 'product_handler_field_product_operations',
    ),
  );

  return $data;
}

На что здесь стоит обратить внимание - на обработчики. В данном примере у нас используются стандартные вьюзовские и несколько самописных.

Обработчики Views

  • views_handler_relationship;
  • views_handler_field_date;
  • views_handler_field_user;
  • views_handler_argument_string;
  • views_handler_filter_date;
  • views_handler_filter_user_name;
  • views_handler_filter_numeric;
  • views_handler_filter_string;
  • views_handler_sort;
  • views_handler_sort_date.

Самописные обработчики

  • product_handler_field_product;
  • product_handler_field_product_status;
  • product_handler_field_product_operations;
  • product_handler_argument_product_id;
  • product_handler_filter_product_status.

Следующим шагом будет написание наших обработчиков. В каталоге views нашего модуля создаем каталог handlers. В этом каталоге для каждого обработчика создаем файл, название которого совпадает с названием обработчика:

  • product_handler_field_product.inc
  • product_handler_field_product_status.inc
  • product_handler_field_product_operations.inc
  • product_handler_argument_product_id.inc
  • product_handler_filter_product_status.inc

Содержимое product_handler_field_product.inc:

<?php

/**
 * @file
 * Contains the product id field handler.
 */

/**
 * Field handler to provide simple renderer that allows linking to a product.
 */
class product_handler_field_product extends views_handler_field {
  function init(&$view, &$options) {
    parent::init($view, $options);
    
    // Если выбрана опция "Выводить в виде ссылки", до достаем id продукта.
    if (!empty($this->options['link_to_product'])) {
      $this->additional_fields['id'] = array('table' => 'product', 'field' => 'id');
    }
  }
  
  /**
   * Обьявляем настройки для поля.
   */
  function option_definition() {
    $options = parent::option_definition();
    $options['link_to_product'] = array('default' => 'none');
    return $options;
  }

  /**
   * Описываем форму с натсройками.
   */
  function options_form(&$form, &$form_state) {
    parent::options_form($form, $form_state);

    $form['link_to_product'] = array(
      '#title' => t('Link this field to the original piece of product'),
      '#description' => t("Enable to override this field's links."),
      '#type' => 'checkbox',
      '#default_value' => !empty($this->options['link_to_product']),
    );

    parent::options_form($form, $form_state);
  }

  /**
   * Выводим поле в виде ссылки на продукт.
   */
  function render_link($data, $values) {
    if (!empty($this->options['link_to_product']) && !empty($this->additional_fields['id'])) {
      $id = $this->get_value($values, 'id');
      $this->options['alter']['make_link'] = TRUE;
      $this->options['alter']['path'] = 'product/' . $id;
    }

    return $data;
  }

  /**
   * Выводим поле.
   */
  function render($values) {
    $value = $this->get_value($values);
    return $this->render_link($this->sanitize_value($value), $values);
  }
}

Содержимое product_handler_field_product_status.inc:

<?php

/**
 * @file
 * Contains the product status field handler.
 */

/**
 * Field handler a product status into its readable form.
 */
class product_handler_field_product_status extends product_handler_field_product {
  function render($values) {
    $id = $this->get_value($values);
    $value = product_status_get_title($id);
    return $this->render_link($this->sanitize_value($value), $values);
  }
}

Содержимое product_handler_field_product_operations.inc:

<?php

/**
 * @file
 * Contains the product's operations field handler.
 */

/**
 * Field handler to present a product's operations links.
 */
class product_handler_field_product_operations extends views_handler_field {
  function construct() {
    parent::construct();

    $this->additional_fields['id'] = 'id';
  }

  function option_definition() {
    $options = parent::option_definition();
    $options['add_destination'] = TRUE;
    return $options;
  }

  function options_form(&$form, &$form_state) {
    parent::options_form($form, $form_state);

    $form['add_destination'] = array(
      '#type' => 'checkbox',
      '#title' => t('Add a destination parameter to edit and delete operation links so users return to this View on form submission.'),
      '#default_value' => $this->options['add_destination'],
    );
  }

  function query() {
    $this->ensure_my_table();
    $this->add_additional_fields();
  }

  function render($values) {
    $id = $this->get_value($values, 'id');

    $links = array();

    $links['edit'] = array(
      'title' => t('Edit'),
      'href' => 'product/' . $id . '/edit',
      'html' => FALSE,
    );

    $links['delete'] = array(
      'title' => t('Delete'),
      'href' => 'product/' . $id . '/delete',
      'html' => FALSE,
    );

    if ($this->options['add_destination']) {
      $links['edit']['query'] = drupal_get_destination();
      $links['delete']['query'] = drupal_get_destination();
    }

    return theme('links', array('links' => $links, 'attributes' => array('class' => array('links', 'inline', 'operations'))));
  }
}

Содержимое product_handler_argument_product_id.inc:

<?php

/**
 * @file
 * Provide a product id argument handler.
 */

/**
 * Argument handler to accept a product id.
 */
class product_handler_argument_product_id extends views_handler_argument_numeric {
  /**
   * Override the behavior of title().
   */
  function title_query() {
    $titles = array();

    $result = db_query("SELECT p.title FROM {product} p WHERE p.id IN (:ids)", array(':ids' => $this->value));
    foreach ($result as $product) {
      $titles[] = check_plain($product->title);
    }

    return $titles;
  }
}

Содержимое product_handler_filter_product_status.inc:

<?php

/**
 * @file
 * Provide a product status filter handler.
 */

/**
 * Filter by product status.
 */
class product_handler_filter_product_status extends views_handler_filter_in_operator {
  function get_value_options() {
    if (!isset($this->value_options)) {
      $this->value_title = t('Status');
      $this->value_options = array();
      foreach (product_status_get_title() as $key => $title) {
        $this->value_options[$key] = $title;
      }
    }
  }
}

Далее открываем файл product.info и добавляем в него следующие строки:

dependencies[] = views

files[] = views/handlers/product_handler_field_product.inc
files[] = views/handlers/product_handler_field_product_status.inc
files[] = views/handlers/product_handler_field_product_operations.inc
files[] = views/handlers/product_handler_filter_product_status.inc
files[] = views/handlers/product_handler_argument_product_id.inc

Вот и все. Теперь наша сущность связана с Views.

Комментарии (9)

Аватар пользователя Гость
Гость

Я так понимаю 2-способ устаревший?

Аватар пользователя Benya
Benya

Не совсем, первый способ просто использует стандартный класс модуля Entity API и по умолчанию предоставляет не такой широкий функционал, но зато занимает минут 5 на реализацию (можно сказать автоматизированный). Я лично использую второй способ, потому что можно самому контролировать, какие поля, фильтры, связи и т.д будут доступны в UI, чего не скажешь о первом способе. Хотя можно заморочиться и сделать 3м способом, с помощью написания своего класса.

Аватар пользователя Sergey Krasulevskiy
Sergey Krasulevskiy

маленькое уточнение, в инфо файле не нужно писать эту строку - files[] = views/product.views.inc

Аватар пользователя Benya
Benya

Спс за внимательность, поправил

Аватар пользователя Petya Shmetya
Petya Shmetya

Создавал сущности с помощью модуля Entity Construction Kit (ECK) и они сразу видны во вьюсах.

Аватар пользователя товарищ Дмитрий
товарищ Дмитрий

Благодарю за статью, очень помогла, теперь думаю как создать обратную связь: нужно, чтобы таблица была доступна из представления типа пользователь. Если, например, прописать join приемлемо ли это для базовых таблиц?

Аватар пользователя Benya
Benya

Вы создаете вьюху для вывода юзеров и хотите загрузить все сущности которые создал юзер я правильно понял?

Аватар пользователя товарищ Дмитрий
товарищ Дмитрий

Примерно так, только не сущности, а просто завели кучу объектов, и теперь нужно смотреть кто что творит))

Аватар пользователя Benya
Benya

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