Программное создание полей в Drupal 7

Drupal по праву считают очень гибкой CMS, а иногда и вовсе называют CMF. Обилие модулей и возможностей ядра позволяет решать огромный спектр прикладных задач при создании сайта. Сейчас я бы хотел рассмотреть один часто встречающийся случай.

Нам нужно запрограммировать поле и связать его с определенной сущностью.

Сперва рассмотрим простой вариант, когда требуется создать поле уже существующего типа, с существующими вариантами отображения: text, textarea, select и т.д.

Для этого потребуется создать файл testmodule.install и реализовать хук-функцию hook_install().

//testmodule.install

/**
 * Implements hook_install().
 */
function testmodule_install() {
  // Our field named ''field_testmodule_field''
  $field_name = 'field_testmodule_field';

  // Check out existing information of this field
  if (!field_info_field($field_name)) {
    field_info_cache_clear();

    $field = array(
      'field_name' => $field_name, 
      'type' => 'text',  // Already existed field type
      'label' => t('TestModule custom field'),
    );
    field_create_field($field);
  }

  // Now, when we have created our field, we must create at least one instance of it in any entity	
  $instance['field_name'] = $field_name;
  $instance['entity_type'] = 'node';
  $instance['bundle'] = 'article';
  field_create_instance($instance); 
}

В первой части функции мы создали само поле как абстракцию, а во второй создали его экземпляр в node типа bundle. После установки модуля поле появится в указанном типе сущности. Будут созданы две таблицы field_data_field_testmodule_field и field_revision_field_testmodule_field, которые будут содержать набор служебных полей, как и в других аналогичных таблицах.

Вопрос: Можно ли привязывать новое поле не только к нодам?

Да, можно, например к field_collection или к любому другому типу сущностей. Для этого нужно заменить две строчки при создании экземпляра поля:

  $instance['entity_type'] = 'field_collection_item';
  $instance['bundle'] = 'field_collection_name';

Если же, требуемое поле должно обладать новым типом и способом отображения, то в вышеприведенный код нужно внести ряд дополнений: дописать созданную функцию и создать hook_field_schema() для того, чтобы описать свойства самой сущности поля такие как внутренние поля, тип, отображение и вид формы внесения данных.

//testmodule.install
/**
 * Implements hook_install().
 */
function testmodule_install() {
  // Our field named ''field_testmodule_field''
  $field_name = 'field_testmodule_field';

  // Check out existing information of this field
  if (!field_info_field($field_name)) {
    field_info_cache_clear();

    $field = array(
      'field_name' => $field_name,
      'type' => 'testmodule_field_value',
      'label' => t('TestModule custom field'),
      'cardinality' => '1',  // number of widget to be applued in form by default
      'widget' => array(
        'type' => 'field_testmodule_field_widget', // hook_widget
        'settings' => array(
          'multiple_value_widget' => 'table',
        )
      ),
      'formater' => array(
        'type' => 'field_testmodule_field_formatter', // hook_formatter
      ),
    );
    field_create_field($field);
  }

  // Now, when we have created our field, we must create at least one instance of it in any entity	
  $instance = field_info_field($field_name);
  $instance['entity_type'] = 'node';
  $instance['bundle'] = 'article';
  field_create_instance($instance); 
}

/**
 * Implements hook_field_schema().
 */
function testmodule_field_schema($field) {
	if($field['type'] == 'testmodule_field_value'){
     $columns = array(
	 'field_1' => array(
	   'type' => 'int',
	   'unsigned' => TRUE,
	   'not null' => FALSE,
	 ),
	 'field_2' => array(
	   'type' => 'varchar',
	   'length' => '256',
       'not null' => FALSE,
       'default' => '',
	 ),
	);
	return array('columns' => $columns);
	}
}

В testmodule_field_schema мы определили набор вложенных полей, которые будут добавлены в базу данных. Пример существующего поля с похожей структурой — image с полями fid, description и format. Теперь мы должны описать как будет выглядеть форма внесения данных в поле и отображение информации, хранящейся в поле. Для этого мы создаем файл testmodule.module и продолжаем работать в нем.

Для корректного описания поля нам нужно определить ряд хук-функций:

Вопрос: Есть ли среди этих хуков необязательные?

Да, hook_field_validate()

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

//testmodule.module
/**
 * Implements hook_field_info().
 */
function testmodule_field_info() {
  return array(
    'testmodule_field_value' => array(
      'label' => t('Test field'),
      'property_type' => 'testmodule_field_value',
      'property_callbacks' => array('testmodule_field_value_property_info_callback'),
      'description' => t('PID relation select'),
      'default_widget' => 'field_testmodule_field_widget',
      'default_formatter' => ''field_testmodule_field_formatter',
    ),
  );
}

function testmodule_field_value_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];

  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
  unset($property['query callback']);

  $schema = field_info_field('field_testmodule_field');

  foreach ($schema['columns'] as $key => $val)
  {
    $property['property info'][$key] = array(
      'label' => $key,
      'type' => 'text',
      'setter callback' => 'entity_property_verbatim_set',
    );
  }
}

/**
 * Implements hook_field_validate().
 */
function testmodule_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  
}

/**
 * Implements hook_field_is_empty().
 */
function testmodule_field_is_empty($item, $field) {

  if (empty($item['field_1'])) {
    return TRUE;
  }

  if (empty($item['field_2'])) {
    return TRUE;
  }
}

/**
 * Implements hook_field_widget_info().
 */
function testmodule_field_widget_info() {
  return array(
      'field_testmodule_field_widget' => array(
          'label' => t('Test field widget'),
          'field types' => array('field_testmodule_field'),
      ),
  );
}

/**
 * Implements hook_field_formatter_info().
 */
function nova_statistics_field_formatter_info() {
  return array(
      'field_testmodule_field_formatter' => array(
          'label' => t('Test field formatter'),
          'field types' => array('field_testmodule_field'),
      ),
  );
}

/**
 * Implements hook_field_widget_form().
 */
function testmodule_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $list = array (' - ', 'first_property', 'second_property');


  $element = array(
      '#type' => 'container',
      '#attributes' => array('class' => array('field-item inline-fields')),
  );
	//var_dump($instance['widget']['type']);die();
  switch ($instance['widget']['type']) {
	
    case 'field_testmodule_field_widget':	  
      $element['field_1'] = array(
          '#type' => 'select',
          '#title' => 'Field 1:',
          '#description' => isset($element['#description']) ? $element['#description'] : '',
          '#default_value' => isset($items[$delta]['field_1']) ? $items[$delta]['field_1'] : '',
          '#required' => isset($element['#required']) ? $element['#required'] : '',
          '#options' => $list,
          '#weight' => isset($element['#weight']) ? $element['#weight'] : 0,
          '#delta' => $delta,
      );
      $elemen['field_1']['#attributes']['class'][] = 'inline-field';

      $element['field_2'] = array(
          '#type' => 'textfield',
          '#title' => 'Field 2:',
          '#size' => 12,
          '#description' => isset($element['#description']) ? $element['#description'] : '',
          '#default_value' => isset($items[$delta]['field_2']) ? $items[$delta]['field_2'] : '',
          '#required' => isset($element['#required']) ? $element['#required'] : '',
          '#weight' => isset($element['#weight']) ? $element['#weight'] : 0,
          '#delta' => $delta,
      );
      $element['field_2']['#attributes']['class'][] = 'inline-field';

      break;
  }

  return $element;
}

/**
 * Implements hook_field_formatter_view().
 */
function  testmodule_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {

  $element = array();
  switch ($display['type']) {

    case 'field_testmodule_field_formatter':

      foreach ($items as $delta => $item) {

        switch ($item) {
          case 'field_1':
            $formattedText = $item['field_1'];
            $element[$delta]['#markup'] = $formattedText;
            break;

          case 'field_2':
            $formattedText = $item['field_2'];
            $element[$delta]['#markup'] = $formattedText;
            break;
        }
      }

      break;
  }
  return $element;
}

Вопрос: Как сделать это поле множественным?

Данный параметр уже можно настроить через интерфейс администратора в настройках поля.

Если вам понравилась данная статья или остались вопросы, то воспользуйтесь формой комментариев ниже.

Поделиться