Построение персональных сущностей на Drupal

Сущности (entities) – отличный способ организации данных в Drupal. Если вы знакомы с узлами (node), термами в таксономии, комментариями или пользователями в Drupal, вы уже должны знать, что все эти вещи, начиная с 7 версии Drupal, являются сущностями.

Другим важным аспектом организации является то, что эти сущности могут быть оснащены полями (fieldable) через программный интерфейс работы с полями – Field API.

В этом уроке я покажу, как можно определить свой собственный тип сущности и начать работать с ним. Зачем создавать свою сущность, если можно воспользоваться узлами?

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

В ходе урока мы создадим персональный тип сущности, который назовём project, предназначенный для отображения информации о проекте: название, описание и крайний срок выполнения. Потом мы рассмотрим некоторые приёмы работы с персональными сущностями.

Для понимания материала вам пригодятся основные навыки построения собственных модулей. (Наш модуль будет называться demo). Если вы готовы, напишите файл .info и создайте пустые файлы .module и .install.

Я настроил Git-репозиторий, откуда вы можете загрузить исходные коды для нашего урока. Наш урок будет состоять из двух частей; для каждой части я создал по ветке в Git.

Также для работы с примерами вам потребуется установить на вашем сайте дополнительный модуль Entity API и установить его как зависимость в наш модуль. Entity API обладает мощными средствами для работы с сущностями, которых не хватает ядру Drupal.

Определение собственного типа сущности

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

В моём файле demo.install содержится следующий код:

/**
 * Implements hook_schema().
 */
function demo_schema() {

  $schema = array();

  $schema['demo_projects'] = array(
    'description' => 'The base table for the Project entity',
    'fields' => array(
      'id' => array(
        'description' => 'Primary key of the Project entity',
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ),
      'name' => array(
        'description' => 'Project name.',
        'type' => 'varchar',
        'length' => 255,
        'not null' => FALSE,
      ),
      'description' => array(
        'description' => 'Project description.',
        'type' => 'text',
        'size' => 'big',
        'not null' => FALSE,
        'default' => NULL
      ),
      'deadline' => array(
        'description' => 'Project deadline.',
        'type' => 'int',
        'length' => 11,
        'not null' => FALSE,
      ),
    ),
    'primary key' => array('id'),
  );

  return $schema;
}

Это довольно простая реализация обратного вызова hook_schema(), при помощи которой мы создаём таблицу demo_projects, которая имеет 4 столбца: id, name, description и deadline, причём первое поле является ключом. Ничего особенного.

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

Вот соответствующий код из файла demo.module:

/**
 * Implements hook_entity_info().
 */
function demo_entity_info() {

  $info = array();

  $info['project'] = array(
    'label' => t('Project'),
    'base table' => 'demo_projects',
    'entity keys' => array(
      'id' => 'id',
      'label' => 'name',
    ),
    'module' => 'demo',
  );

  return $info;
}

Поле массива $info, возвращаемого этим обработчиком, содержит машинно-читаемое имя сущности. В массиве определены ещё несколько опций, и в ходе урока мы добавим еще больше.

Пока мы остановимся на полях 'label' – человеко-читаемое имя сущности, 'base table' – поле для хранения данных сущности, 'entity keys' – свойства сущности, служащие для идентификации, и 'module' – указывает на модуль, определяющий тип сущности. Последнее поле необязательно, но его рекомендуется указать.

Таким образом, мы зарегистрировали наш простой тип сущности в Drupal. Чтобы проверить свою работу, активируйте этот модуль и убедитесь, что таблица в базе данных создана.

Затем заполните таблицу несколькими полями для будущих тестов:

INSERT INTO `demo_projects` (`id`, `name`, `description`, `deadline`)
VALUES
    (1, 'Summer House', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', 1397501105),
    (2, 'Winter House', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', 1397501132);

Наконец, зарегистрируйте путь в Drupal с использованием hook_menu() (для проверки подойдёт любой путь) и создайте следующую функцию обратного вызова:

$projects = entity_load('project', array(1, 2));
dpm($projects);
return 'Some string';

Сначала мы воспользуемся entity_load(), чтобы загрузить сущности типа project с номерами 1 и 2, затем мы выведем их на экран при помощи функции dpm().

Чтобы эта функция сработала, вывод отладочной информации (Devel) в Drupal должен быть разрешён. И не забудьте, что callback страницы должен вернуть какое-то значение, иначе он не запустится.

Теперь, если вы перейдёте на страницу, заданную зарегистрированным ранее путём, вы увидите данные из таблицы нашего модуля в отладочном выводе, сформированном Krumo.

В качестве альтернативы вы можете воспользоваться классом EntityFieldQuery, чтобы получить любое свойство сущности, а не только ключ.

Класс и контроллер сущности

К сожалению, функции для работы с сущностями в Drupal не отличаются разнообразием. Точнее говоря, entity_load() является единственной такой функцией. Однако модуль Entity API исправляет этот недостаток.

Чтобы воспользоваться Entity API, мы должны изменить информацию о типе сущности и задать классы, которые будут работать с сущностями.

Пока что мы добавим 2 ключа в массив project, заданный обработчиком hook_entry_info():

'entity class' => 'Entity',
'controller class' => 'EntityAPIController',

Первый заданный нами класс содержится в Entity API и оборачивает некоторые функции для выполнения их от лица сущности.

Этот класс объявлен в файле entity.inc, и если мы заглянем внутрь этого файла, то заметим, что многие его методы транслируют вызовы методам другого класса – класса controller, который мы указали в ключе 'controller class'.

Класс EntityAPIController, реализованный в файле entity.controller.inc, предлагает некоторые осмысленные действия, производимые над сущностями по умолчанию.

Он расширяет базовый класс Drupal DrupalDefaultEntityController и отвечает, помимо прочего, за CRUD-операции (создание, редактирование и удаление сущностей).

Оба этих класса могут быть расширены в вашем собственном модуле, чтобы уточнить функции вроде поиска, загрузки или отображения сущности. Скоро мы узнаем, как это делается.

Но сначала я покажу вам, как сохранить новую сущность. Пока что в нашей таблице есть две сущности с идентификаторами 1 и 2.

Я хочу дописать тестовый код отображения страницы так, чтобы он мог создать сущность с идентификатором 3, если её ещё не существует. Это может выглядеть так:

$projects = entity_load('project', array(1, 2, 3));

 if (!isset($projects[3])) {
   $entity = entity_create('project', array('id' => 3));
   $entity->name = t('Spring House');
   $entity->description = t('Some more lipsum.');
   $entity->save();
 }

 dpm($projects);

 return 'Some string';

Как видите, теперь мы загружаем 2 сущности и проверяем существование третьей. Если её не существует, мы используем вспомогательную функцию Entity API entity_create(), устанавливаем свойства сущности и используем метод сущности save(), чтобы занести её в базу данных.

Этот метод предоставлен классом Entity, и является обёрткой одноимённого метода класса-контроллера. Именно в контроллере реализована логика сохранения данных. Но эти детали происходят за сценой, так что нам не нужно о них беспокоиться.

Если вы обновите нашу страницу, на ней по-прежнему будут две сущности. Но перезагрузите страницу ещё раз, и сущностей станет 3.

Переопределение классов сущностей

Последнее, что я хочу показать вам в этой части нашего урока это то, как отображать сущности собственного типа. Для этого мы ещё немного поработаем над обработчиком отображения страницы, заставив его отображать список наших сущностей.

Но сначала мы должны переопределить метод класса-контроллера по умолчанию buildContent(). Это необходимо сделать, потому что контроллер не может ничего знать о том, как устроены наши данные, так что мы должны предоставить ему некоторую информацию о том, как их отображать.

Прежде всего, объявим собственный класс контроллера, который расширит контроллер по умолчанию:

/**
 * Extending the EntityAPIController for the Project entity.
 */
class ProjectEntityController extends EntityAPIController {

}

Я выбрал нашему классу имя ProjectEntityController. Теперь мы должны заменить значение ключа 'controller class' в нашей реализации hook_entity_info() на имя нового класса. Не забывайте об этом действии.

В реализации нашего класса мы позаимствуем имя метода и возвращаемое методом значение у оригинального класса:

public function buildContent($entity, $view_mode = 'full', $langcode = NULL, $content = array()) {

$build = parent::buildContent($entity, $view_mode, $langcode, $content);

// Our additions to the $build render array

return $build;

}

Пока мы не сделали ничего нового, но теперь мы можем добавить свои данные к возвращаемому методом значению, которое представляет собой не что иное, как стандартный массив Drupal – render array.

Так что мы можем написать следующий код непосредственно перед возвратом $build:

$build['description'] = array(
  '#type' => 'markup',
  '#markup' => check_plain($entity->description),
  '#prefix' => '<div class="project-description">',
  '#suffix' => '</div>',
);
$build['deadline'] = array(
  '#type' => 'markup',
  '#markup' => date('d F, Y', check_plain($entity->deadline)),
  '#prefix' => '<p>Deadline: ',
  '#suffix' => '</p>',
);

Мы добавили в метод две вещи. Во-первых, вывод содержимого поля description оборачивается тэгом <div class="project-description">. А во-вторых, мы выводим форматированную дату в тэге параграфа.

Это соответствует оформлению дефолтной темы Drupal, так что, если ничего не понимаете в этой разметке, оставьте пока всё как есть. Но вы наверняка заметили, что мы не использовали поле с названием проекта.

Оно будет выводиться Drupal автоматически, так как мы указали его как label в 'entity keys', в обработчике hook_entry_info().

Последним шагом будет модификация функции отображения страницы, чтобы она показывала наши сущности. Вот простой и быстрый способ, подходящий для целей демонстрации:

$projects = entity_load('project', array(1, 2, 3));

$list = entity_view('project', $projects);

$output = array();

foreach ($list['project'] as $project) {
  $output[] = drupal_render($project);
}

return implode($output);

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

Эта функция возвращает список массивов отображения (render array), по массиву на каждую сущность. Мы отображаем каждую сущность и складываем результат в массив $output, который разворачиваем перед выходом.

Обновите страницу, чтобы полюбоваться на результат - список сущностей, которые мы создали. Если результат не такой, как ожидается, убедитесь на всякий случай, что все кэши очищены.

Заключение

В этом уроке мы научились создавать сущности Drupal в коде. Мы поняли, как написать схему для данных сущности и зарегистрировать эту схему в Drupal. Мы применили всю мощь модуля Entity API к работе с сущностями в объектно-ориентированном стиле.

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

Во-вторых, мы оснастим наши сущности пользовательскими полями через интерфейс Drupal. В-третьих, мы обеспечим доступ к нашим сущностям через модуль Views, чтобы осуществлять к ним стандартные запросы и включать их в списки.

Оставайтесь с нами!

РедакцияПеревод статьи «Build Your Own Custom Entities in Drupal – Setup»